当前位置: 首页 > news >正文

在进行网站设计时宣传网站设计

在进行网站设计时,宣传网站设计,使用wordpress的购物网站,wordpress 以年—月目录形式组织上传内容STM32单片机项目实例#xff1a;基于TouchGFX的智能手表设计#xff08;3#xff09;嵌入式程序任务调度的设计 目录 一、嵌入式程序设计 1.1轮询 1.2 前后台#xff08;中断轮询#xff09; 1.3 事件驱动与消息 1.3.1 事件驱动的概念 1.4 定时器触发事件驱动型的任…STM32单片机项目实例基于TouchGFX的智能手表设计3嵌入式程序任务调度的设计 目录 一、嵌入式程序设计 1.1轮询 1.2 前后台中断轮询 1.3 事件驱动与消息 1.3.1 事件驱动的概念 1.4 定时器触发事件驱动型的任务设计 ​​​​​​​1.4.1定时器触发 ​​​​​​​1.4.2 界面事件驱动 一、嵌入式程序设计 大数学家华罗庚先生在《统筹方法》中写到自己泡茶的故事也就是大家语文课本的《时间统筹法》一文在这个故事中时间统筹法主要是用来做时间管理优化做事情的流程节约时间。比如洗开水壶、烧水需要16分钟洗茶壶、洗茶杯、拿茶叶需要4分钟这两件事情先做哪个这是最常见的家务活举例不同的思维方式产生不同结果。 如果按照线性思维先去洗开水壶、烧水需要16分钟再去洗茶壶、洗茶杯和拿茶叶需要4分钟那一共需要16420分钟按照时间统筹法先洗开水壶把水放在炉子上烧然后同时去洗茶壶、茶杯、拿茶叶等水烧好了茶具也准备好了这样两件事情一共只需要花费11516分钟无形中就节约了4分钟的时间。 图 1-1 《时间统筹法》中的家务活举例 用嵌入式系统去看泡茶这件事情水壶、茶壶、茶杯、茶叶等可以理解为嵌入式系统中的硬件层洗、拿、烧的动作理解为嵌入式系统中的驱动层泡茶理解为嵌入式系统中的应用层。 图 1-2 从嵌入式系统的角度看待泡茶故事 泡茶故事中如按照线性思维去操作泡茶需要经过洗水壶 - 烧开水 - 洗茶壶 - 洗茶杯  - 拿茶叶  - 泡茶总共6个过程这些过程我们换个词用“状态”去表示洗水壶状态- 烧开水状态 - 洗茶壶状态 - 洗茶杯状态  - 拿茶叶状态  - 泡茶状态这些状态间的“迁移”依赖于某一时刻发生的有意义的事情例如洗水壶完成、烧开水完成…进而发生了状态的迁移我们称之为“事件”。在状态的“迁移”过程中我们需要做出其它一些行为这些行为就是“动作”例如拿水壶、拿茶壶或者拿茶杯等“动作”是对事件的响应。对于事件的响应还依赖于是否满足一定的“条件”才能发生状态间的迁移并不是有求必应的。泡茶的过程在嵌入式程序设计中可以用嵌入式状态机FSM模式进行设计一款C语言编写的轻量级的函数指针有限状态机编程框架可实现entry和exit动作嵌入式状态机是一种基于状态转移的程序设计模式它通过将程序的执行过程分成一系列状态以及描述状态转移的规则实现复杂问题的分步解决。在嵌入式系统中状态机常用来实现复杂的控制逻辑、事件处理和通信协议等功能其简单灵活的设计在嵌入式系统应用中得到了广泛的运用。 图 1-3 有限状态机在泡茶故事中的使用  嵌入式系统的应用场景比泡茶的过程更为复杂。例如硬件方面处理器的单核与多核、外部设备对响应速度与周期性控制、低功耗等要求等。软件方面裸机与嵌入式系统RTOS、Linux开发的不同以及是否使用中间件TouchGFX等。在这些场景下就需要具备嵌入式程序设计的思想和方法。本文对微控制器裸机任务开发的设计方法进行探讨主要涉及应用程序的轮询、前后台、优先级与时间片、有限状态机、定时器触发事件驱动型的任务调度进行讲解。 1.1轮询 对于简单的应用程序轮询无限循环的实现比较简单在硬件完成初始化后顺序的完成各种任务。在外设的基础实验中常采用这种方式。 轮询的伪代码实现方式 01 int main(void) 02 { 03 /* 硬件相关初始化 */ 04 HardwareInit(); 05 06 /* 无限循环 */ 07 while(1) { 08 /* 任务1 */ 09 Task1(); 10 11 /* 任务2 */ 12 Task2(); 13 14 /* 任务3 */ 15 Task3(); 16 } 17 } 在实际的嵌入式系统中存在周期性周期100ms处理时间10ms与触发型任务扫地机器人悬空检测实时性每个任务的执行时间与实时响应要求不同在采用轮询系统进行程序设计时很难应对这些场景。 1.2 前后台中断轮询 前后台系统是在轮询的基础上加入了中断。外部事件的记录在中断中操作对事件的响应在轮询中完成中断处理过程称之为前台main函数中的轮询称为后台。如下图所示 后台的程序顺序执行如果产生中断那么中断会打断后台程序的正常执行转而去执行中断服务程序。如果事件的处理过程比较简单可以直接在中断服务程序中进行处理如果事件的处理过程比较复杂可以在中断中对事件响应进行标记进而返回后台程序进行处理。轮询的伪代码实现方式 01 int main(void) 02 { 03 /* 硬件相关初始化 */ 04 HardwareInit(); 05 06 /* 无限循环 */ 07 while(1) { 08 /* 任务1 */ 09 if(Task1标志) 10 { 11 Task1(); 12 Task1标志为假; 13 } 14 15 /* 任务2 */ 16 if(Task2标志) 17 { 18 Task2(); 19 Task2标志为假; 20 } 21 22 /* 任务3 */ 23 if(Task3标志) 24 { 25 Task3(); 26 Task3标志为假; 27 } 28 } 29 } 30 /** 31 ** Task1的中断服务程序 32 **/ 33 void Task1_Handler(void) 34 { 35 Task1标志为真; 36 } 37 /** 38 ** Task1的中断服务程序 39 **/ 40 void Task2_Handler(void) 41 { 42 Task2标志为真; 43 } 44 /** 45 ** Task1的中断服务程序 46 **/ 47 void Task3_Handler(void) 48 { 49 Task3标志为真; 50 } 相较于轮询系统前后台系统可以确保事件的记录不会丢失提高了对事件的响应。同时基于Cortex-M内核的MCU对异常具有可编程的优先级中断嵌套、末尾连锁以及延迟到达等功能这可以大大提高程序的实时响应能力。 采用前后台系统进行程序时对后台的任务需要进行设计避免单个任务长时间占有处理器资源。当任务的逻辑比较复杂任务的拆分难度增加同时随着中断事件的增加整个程序的设计与响应的实时性将会降低。​​​​​​​ 1.3 事件驱动与消息 嵌入式MCU软件开发中我们应具备程序分层设计的思想程序分层设计能够降低软件的复杂度和依赖关系同时有利于标准化便于管理各层的程序提高各层逻辑的复用软件工程技术中的复用与解耦复用可以极大提升软件开发效率使得软件开发可以从 70% 甚至 90% 起步而解耦可以大幅提升软件的可维护性和可修改性降低长期维护成本。 ​​​​​​​1.3.1 事件驱动的概念 Hello,World!是很多初学者进行嵌入式操作系统编程时的第一个程序。在嵌入式MCU裸机编程中UART外设要比GPIO外设更为复杂初学者的第一个程序往往是点亮LED灯点灯大师在GPIO的输入操作时通过按键输入去控制LED伪代码实现方式如下 01 int main(void) 02 { 03 /* 硬件相关初始化 */ 04 HardwareInit(); 05 06 /* 无限循环 */ 07 while(1) 08 { 09 /* 按键扫描 */ 10 if(Key为低) 11 { 12 delay(100ms);//延时100ms电平稳定后读取 13 if(Key为低) 14 { 15 LED点亮; 16 } 17 } 18 else 19 { 20 LED熄灭; 21 } 22 /* 其它任务 */ 23 ...... 24 } 25 } 采用该种方式进行程序结构设计时按键输入的响应依赖于其它任务的执行时间与任务的数量若其它任务的执行时间是200ms则可能造成按键事件的丢失。在学习EXTI部分的知识后可以采用中断的方式进行按键事件的响应伪代码实现方式如下 01 int main(void) 02 { 03 /* 硬件相关初始化 */ 04 HardwareInit(); 05 06 /* 无限循环 */ 07 while(1) 08 { 09 /* 其它任务 */ 10 ...... 11 } 12 } 13 /****************************************************************** 14 *FuncName :EXTIx_IRQHandler 15 *Description :EXTIx中断服务函数 16 *Arguments :void 17 *Return :void 18 *******************************************************************/ 19 void EXTIx_IRQHandler ( void ) 20 { 21 ...... 22 /* 按键扫描 */ 23 if(Key为低) 24 { 25 delay(100ms);//延时100ms电平稳定后读取 26 if(Key为低) 27 { 28 LED点亮; 29 } 30 } 31 else 32 { 33 LED熄灭; 34 } 35 ...... 36 } 采用该种方式提高了系统对按键输入的响应同时也存在优先级设置的问题在采用STM32的HAL库开发中HAL_Delay()延时函数默认采用系统滴答定时器(Systick)的中断产生计时。上述代码能够正常运行的前提是Systick的中断优先级要比外部中断线的优先级高同时按键的触发频率也不能太高这些要求在复杂的系统中很难得到满足。 注释T1-T4时间内其它低优先级中断被挂起降低了系统响应性同时阻塞了其它任务的执行。T2-T3时间内处理器的资源被浪费。 对于中断的处理机制减少中断响应时间是设计的初衷我们将思路进行调整当按键按下这个事件发生时在中断中对事件进行记录在主循环中对记录的事件进行处理伪代码实现方式如下 01 # define FLG_KEY 0x08 02 volatile uint8_t gu8EvntFlgGrp 0 ; /*事件标志组*/ 03 int main(void) 04 { 05 uint8_t pu8FlgTmp 0 ; 06 /* 硬件相关初始化 */ 07 HardwareInit(); 08 09 /* 无限循环 */ 10 while(1) 11 { 12 pu8FlgTmp read_envt_flg_grp(); /*读取事件标志组*/ 13 // 14 if (pu8FlgTmp) /*是否有事件发生 */ 15 { 16 if (pu8FlgTmp FLG_KEY) 17 { 18 LED点亮; 19 } 20 } 21 else 22 { 23 LED熄灭; 24 ; /* 空闲代码 */ 25 } 26 27 } 28 } 29 /****************************************************************** 30 *FuncName :read_envt_flg_grp 31 *Description :读取事件标志组 gu8EvntFlgGrp,读取完毕后将其清零。 32 *Arguments :void 33 *Return :void 34 *******************************************************************/ 35 uint8_t read_envt_flg_grp ( void ) 36 { 37 uint8_t pu8FlgTmp 0 ; 38 /* 关闭全局中断 */ 39 __disable_irq(); 40 /* 读取标志组 */ 41 pu8FlgTmp gu8EvntFlgGrp; 42 /* 清零标志组 */ 43 gu8EvntFlgGrp 0 ; 44 /* 开启全局中断 */ 45 __enable_irq(); 46 //返回值 47 return pu8FlgTmp; 48 } 49 /****************************************************************** 50 *FuncName :EXTIx_IRQHandler 51 *Description :EXTIx中断服务函数 52 *Arguments :void 53 *Return :void 54 *******************************************************************/ 55 void EXTIx_IRQHandler ( void ) 56 { 57 ...... 58 /* 关闭全局中断 */ 59 __disable_irq(); 60 HAL_TIM_Base_Start_IT(htimx); //开启定时器 61 /* 开启全局中断 */ 62 __enable_irq(); 63 ...... 64 } 65 /****************************************************************** 66 *FuncName : TIMx_IRQHandler 67 *Description : TIMx中断服务函数100ms中断一次 68 *Arguments : void 69 *Return : void 70 *******************************************************************/ 71 void TIMx_IRQHandler ( void ) 72 { 73 ...... 74 if(Key为低) 75 { 76 /* 关闭全局中断 */ 77 __disable_irq(); 78 gu8EvntFlgGrp | FLG_KEY; /*设置 KEY 事件标志*/ 79 HAL_TIM_Base_Stop(htimx); //关闭定时器 80 /* 开启全局中断 */ 81 __enable_irq(); 82 } 83 ...... 84 } 注释T1-T2/ T3-T4快速记录/处理按键触发事件。T2-T3时间内处理器的资源被充分利用。 上述的按键任务中按键的按下与释放是一种事件中断处理流程对事件进行记录并产生一个消息主循环中对消息进行执行并销毁是一种比较简单的事件驱动机制。在嵌入式系统中事件驱动机制的应用也十分广泛。下面是UART、TIMER、EXTI与KEY等外设的事件检测在各自中断中完成通过事件驱动机制通知主函数进行处理的完整伪代码。 1 # define FLG_UART 0x01 2 # define FLG_TMR 0x02 3 # define FLG_EXI 0x04 4 # define FLG_KEY 0x08 5 6 volatile uint8_t gu8EvntFlgGrp 0 ; /*事件标志组*/ 7 8 uint8_t read_envt_flg_grp( void ); 9 /************************************************************ 10 *FuncName :main 11 *Description :主函数 12 *Arguments :void 13 *Return :void 14 *************************************************************/ 15 void main ( void ) 16 { 17 uint8_t pu8FlgTmp 0 ; 18 /* 硬件相关初始化 */ 19 HardwareInit(); 20 /* 无限循环 */ 21 while ( 1 ) 22 { 23 pu8FlgTmp read_envt_flg_grp(); /*读取事件标志组*/ 24 // 25 if (pu8FlgTmp ) /*是否有事件发生 */ 26 { 27 if (pu8FlgTmp FLG_UART) 28 { 29 action_uart_task(); /* 处理串口事件 */ 30 } 31 if (pu8FlgTmp FLG_TMR) 32 { 33 action_timer_task(); /* 处理定时中断事件 */ 34 } 35 if (pu8FlgTmp FLG_EXI) 36 { 37 action_exti_task(); /* 处理外部中断事件 */ 38 } 39 if (pu8FlgTmp FLG_KEY) 40 { 41 action_key_task(); /* 处理击键事件 */ 42 } 43 } 44 else 45 { 46 ; /* 空闲代码 */ 47 } 48 } 49 } 50 /****************************************************************** 51 *FuncName :read_envt_flg_grp 52 *Description :读取事件标志组 gu8EvntFlgGrp,读取完毕后将其清零。 53 *Arguments :void 54 *Return :void 55 *******************************************************************/ 56 uint8_t read_envt_flg_grp ( void ) 57 { 58 uint8_t pu8FlgTmp 0 ; 59 /* 关闭全局中断 */ 60 __disable_irq(); 61 /* 读取标志组 */ 62 pu8FlgTmp gu8EvntFlgGrp; 63 /* 清零标志组 */ 64 gu8EvntFlgGrp 0 ; 65 /* 开启全局中断 */ 66 __enable_irq(); 67 //返回值 68 return pu8FlgTmp; 69 } 70 /****************************************************************** 71 *FuncName :UARTx_IRQHandler 72 *Description :UARTx中断服务函数 73 *Arguments :void 74 *Return :void 75 *******************************************************************/ 76 void UARTx_IRQHandler ( void ) 77 { 78 ...... 79 push_uart_rcv_buf(new_rcvd_byte); /*新接收的字节存入缓冲区*/ 80 /* 关闭全局中断 */ 81 __disable_irq(); 82 gu8EvntFlgGrp | FLG_UART; /*设置 UART 事件标志*/ 83 /* 开启全局中断 */ 84 __enable_irq(); 85 ...... 86 } 87 /****************************************************************** 88 *FuncName : TIMx_IRQHandler 89 *Description : TIMx中断服务函数 90 *Arguments : void 91 *Return : void 92 *******************************************************************/ 93 void TIMx_IRQHandler ( void ) 94 { 95 uint8_t u8KeyCode 0 ; 96 ...... 97 /* 关闭全局中断 */ 98 __disable_irq(); 99 gu8EvntFlgGrp | FLG_TMR; /*设置 TMR 事件标志*/ 100 /* 开启全局中断 */ 101 __enable_irq(); 102 ...... 103 u8KeyCode read_key(); /*读键盘*/ 104 if (u8KeyCode) /*有击键操作 */ 105 { 106 push_key_buf(u8KeyCode); /*新键值存入缓冲区*/ 107 /* 关闭全局中断 */ 108 __disable_irq(); 109 gu8EvntFlgGrp | FLG_KEY; /*设置 KEY 事件标志*/ 110 /* 开启全局中断 */ 111 __enable_irq(); 112 } 113 ...... 114 } 115 /****************************************************************** 116 *FuncName :EXTIx_IRQHandler 117 *Description :EXTIx中断服务函数 118 *Arguments :void 119 *Return :void 120 *******************************************************************/ 121 void EXTIx_IRQHandler ( void ) 122 { 123 ...... 124 /* 关闭全局中断 */ 125 __disable_irq(); 126 gu8EvntFlgGrp | FLG_EXI; /*设置 EXI 事件标志*/ 127 /* 开启全局中断 */ 128 __enable_irq(); 129 ...... 130 } 以上事件处理代码可以做成标准的框架代码它能够应对大部分嵌入式裸机编程的情况。同时事件驱动机制采用这样的方式实现也存几点问题需要注意 同事件集中爆发时会丢失后面发生的事件。 不同事件集中爆发会丢失事件发生的顺序。 事件的优先级与多任务并发执行。 上图中T1、T2、T3是各个任务的事件处理函数I1、I2、I3是不同事件触发的IRQ假定I1、I2、I3分别对应E1、E2、E3事件当运行T1的事件处理函数时发生了2次相同的事件T1事件处理函数被中断2次I2在执行的时候连续两次置位了相应的事件标志位。 当T1的事件处理函数完成后顺序执行T2的事件处理函数在T2执期间发生2次不同的事件。T2被中断2次I1和I3执行并置位相应的事件标志位。 执行T1事件处理函数时产生的两次E2事件由于没有缓冲机制在执行T2事件处理函数时会丢失对E2事件的处理也就是我们上面的讲到的同事件集中爆发时会丢失后面发生的事件。 执行T2事件处理函数时产生的E1和E3事件主循环处理事件的顺序是按照程序预先设定的顺序一个一个的处理事件若集中爆发不同事件对于事件的发生顺序与处理顺序会产生不一致的情况若系统对于事件的发生顺序敏感则无法满足。 为了解决事件的丢失与发生顺序可以在与事件相关的 IRQ中把事件加工成消息并把它存储在消息缓冲区中消息缓冲区设计成以“先入先出”方式管理的环形缓冲队列。事件生成的消息总是从队尾入队管理程序读取消息的时候总是从队头读取这样消息在缓冲区中存储的顺序就是事件在时间上发生的顺序先发生的事件总是能先得到响应。IRQ完成这些工作后立即退出。主循环通过查询消息缓冲区将存储的消息信息进行分析与执行最终完成对本次事件的响应。 通过这种方法实现的事件驱动机制能够解决前面提到的那两个问题即不同事件集中爆发时无法记录事件发生的前后顺序。同一事件集中爆发时容易遗漏后面发生的事件。对于第一种情况消息(事件)在缓冲队列中是以“先入先出”的方式存储的存储顺序就代表了事件发生的先后顺序。对于第二种情况 任何被 ISR 捕捉到的事件都会以一个独立的消息实体存入缓冲队列 即使前后两个是同一个事件 只要 ISR 反应够快就不会遗漏事件。实际上 ISR 的主要工作就是填写消息实体 然后将其存入缓冲队列 做这些工作只占用 CPU 很短的时间。 对于事件的优先级与多任务并发执行需求的场景建议采用实时操作系统Real Time Operating System简称RTOSRTOS在调度方式上分为抢占式调度、时间片调度和合作式调度。采用RTOS可以确保在一定的时间内能够执行到所有的任务。 ​​​​​​​1.4 定时器触发事件驱动型的任务设计 采用裸机方式开发基于TouchGFX的智能手表项目了解TouchGFX的相关原理是十分重要的 TouchGFX用户接口遵循Model-View-PresenterMVP架构模式它是Model-View-ControllerMVC模式的派生模式。 两者都广泛用于构建用户接口应用。MVP模式的主要优势是 关注点分离将代码分成不同的部分提供每部分有自己的任务。 这使得代码更简单、可重复使用性更高且更易于维护。 单元测试由于UI的逻辑Presenter独立于视图View因此单独测试这些部分会容易很多。 MVP中定义了下列三个类 Model是一种接口用于定义要在用户界面上显示或有其他形式操作的数据。 View是一种被动接口用于显示数据来自Model并将用户指令事件传给Presenter以便根据该数据进行操作。 Presenter的操作取决于Model和View。 它从存储库Model检索数据并将其格式化以便在视图中显示。 在TouchGFX中从Model类执行与应用非UI部分这里称为后端系统的通信。后端系统是从UI接收事件和将事件输入UI的软件组件例如采集传感器的新测量值。后端系统可作为单独的任务在同一MCU、单独的处理器、云模块或其他硬件上运行。从TouchGFX的角度来看这并不十分重要只要它是能够与之通信的组件。  使用的特定通信协议不受TouchGFX管理。它只提供一个在每个TouchGFX嘀嗒时间调用一次的函数可以在其中处理需要的通信。   屏幕概念 在TouchGFX应用中可以有任意数量的“屏幕”。 TouchGFX中的屏幕是UI元素小工具及其相关业务逻辑的逻辑分组。 屏幕包含两个类包含该屏幕上显示的所有小工具的View类以及包含该屏幕的业务逻辑的Presenter类。 可以选择在单个屏幕的背景下实现整个应用意味着只有一个View和一个Presenter建议将UI的不相关部分分割成不同屏幕原因有两个 1. TouchGFX包含存储器分配方案可自动分配大多数RAM占用量大的屏幕所需的必要RAM。 只会分配必要的量此RAM块将在应用中的所有屏幕之间重复使用。 2. 有多个屏幕会使UI代码的维护容易得多。 定义屏幕 关于应如何将应用划分成多个屏幕并没有具体的规则但有特定的指南也许能帮助您决定应当用哪些屏幕构成您的特定应用。 在视觉和功能上无关的UI区域应保存在不同屏幕中。 如果是十分简单的有主温度读出显示屏和配置菜单的恒温器应用建议创建“主屏幕”用于温度读出并创建“设置屏幕”用于显示配置菜单。主屏幕的视图将包含用于背景图像的小工具几个显示温度的文本区和一个用于切换至配置菜单的按钮。 另一方面用于配置的视图可能包含显示配置选项列表和不同背景图像的小工具。 如果配置菜单能够编辑许多不同类型的设置日期、名称和键盘、温度、单位等此屏幕的复杂性将大幅提升。 当前活动屏幕 由于 TouchGFX 为屏幕分配内存的方式只为最大的 View 和最大的 Presenter 分配一次只能有一个 View 和一个 Presenter 处于活动状态。 因此如果您的恒温器应用程序正在显示温度读数那么配置菜单屏幕不会在任何地方运行实际上甚至没有分配。 如果从“后端”所有执行恒温器实际工作的非UI代码或硬件外设接收到事件则可以将这些事件传递给当前活动屏幕。 由于一些事件将只与应用中的特定屏幕有关因此这提供了有效的隔离方式。 例如只有主屏幕才能处理通知当前温度变化的已接收事件将更新显示当前温度的文本区而由于当前温度未显示在配置屏幕上配置屏幕可以直接丢弃此无关事件。 TouchGFX中的Model-View-Presenter TouchGFX 遵循 Model-View-Presenter Design Pattern描述的Model-View-Presenter 设计模式。 TouchGFX 屏幕概念通过继承自 TouchGFX 中的 View 和 Presenter 类的类与整个 Model-View-Presenter 架构联系在一起。 因此在TouchGFX Designer中将新屏幕添加到应用时会创建新的特定View类和Presenter类以代表该特定屏幕。 TouchGFX应用中MVP类的内容和责任如下 Model Model 类是一个永远存在的单类它有两个用途 1. 保存UI的状态信息。 在切换屏幕时View和Presenter的分配会被清除因此它们不能用于存储在屏幕转换时应当保留的信息。 为此使用Model保存信息。 2. 作为面向后端系统的接口向/从当前活动屏幕发送事件。 Model类是自动设置的具有指向当前活动Presenter的指针。当Model中发生变化时将变化通知当前活动Presenter。这是通过应用中ModelListener接口中的方法来完成的。 TouchGFX Designer生成的新应用将自动拥有可直接供UI使用的Model类。 View View类或者更具体地说派生自TouchGFX View类的类包含了该视图中显示的控件作为成员对象。 它还包含setupScreen和tearDownScreen函数当进入/退出该屏幕时会自动调用它们。 通常会在setupScreen函数中配置控件。 View还将包含指向相关Presenter的指针。 该指针通过框架自动设置。 使用此指针您可以将 UI 事件如按钮单击传达给 Presenter。 Presenter Presenter 类同样一个派生自 TouchGFX Presenter 类的类负责当前活动屏幕的业务逻辑。 它将接收来自模型的“后端”事件以及来自视图的 UI 事件并决定采取何种行动。 例如如果从 Model 接收到警报事件Presenter 可能决定告诉 View 应该显示警报弹出对话框。 基于TouchGFX的智能手表项目的裸机任务采用定时器触发任务活动屏幕事件驱动的方式实现方式如下图所示 采用该框架设计的程序代码逻辑清晰任务仅与活动屏幕下的事件有关缺点是相关任务的设计不能是阻塞的也存在某一时间段会有多个任务需要顺序执行。该框架下的main函数代码 01 int main(void) 02 { 03 /* USER CODE BEGIN 1 */ 04 05 /* USER CODE END 1 */ 06 07 /* MCU Configuration*/ 08 09 /* Reset of all peripherals, Initializes the Flash and the Systick. */ 10 HAL_Init(); 11 12 /* USER CODE BEGIN Init */ 13 14 /* USER CODE END Init */ 15 16 /* Configure the system clock */ 17 SystemClock_Config(); 18 19 /* Configure the System Power */ 20 SystemPower_Config(); 21 22 /* USER CODE BEGIN SysInit */ 23 24 /* USER CODE END SysInit */ 25 26 /* Initialize all configured peripherals */ 27 MX_GPIO_Init(); 28 MX_GPDMA1_Init(); 29 MX_ICACHE_Init(); 30 MX_OCTOSPI1_Init(); 31 MX_SPI1_Init(); 32 MX_CRC_Init(); 33 MX_I2C1_Init(); 34 MX_TIM16_Init(); 35 MX_TIM17_Init(); 36 MX_USART1_UART_Init(); 37 MX_RTC_Init(); 38 MX_ADC1_Init(); 39 MX_UART5_Init(); 40 MX_SPI2_Init(); 41 MX_TouchGFX_Init(); 42 /* USER CODE BEGIN 2 */ 43 //ESP8266初始化HAL库使用USART3 44 ESP8266_Init(huart5,(uint8_t *)gRX3_BufF,115200); 45 ap3216c_init(); //环境光传感器初始化 46 ILI9341_Init(); //显示屏初始化 47 FT6336_init(); //触摸屏初始化 48 mpu_init_dmp(); //mpu6050 dmp初始化 49 System_Time_init(); 50 //NOR Flash初始化 51 OSPI_W25Qxx_Init(); //初始化W25Q128 52 OSPI_W25Qxx_mmap(); //设置为内存映射模式 53 HAL_PWREx_EnableVddA(); 54 HAL_PWREx_EnableVddIO2(); 55 56 //清空任务列表 57 for(gTaskIndex 0;gTaskIndex OS_TASKLISTCNT;gTaskIndex) 58 g_OSTsakList[gTaskIndex]NULL; 59 60 //读取ADC值 61 if (HAL_ADC_Start_DMA(hadc1,(uint32_t *)gStruADC,ADC_CONVERTED_DATA_BUFFER_SIZE) ! HAL_OK) 62 {Error_Handler();} 63 /* USER CODE END 2 */ 64 65 /* Infinite loop */ 66 /* USER CODE BEGIN WHILE */ 67 HAL_TIM_Base_Start_IT(htim16);//开启定时器16开启,系统任务调度开始 68 HAL_TIM_Base_Start_IT(htim17);//开启定时器17开启,设备控制任务开始 69 while (1) 70 { 71 /* USER CODE END WHILE */ 72 73 MX_TouchGFX_Process(); 74 /* USER CODE BEGIN 3 */ 75 //执行任务列表中的的任务 76 for(gTaskIndex 0;gTaskIndex OS_TASKLISTCNT;gTaskIndex) 77 { 78 if((*g_OSTsakList[gTaskIndex]) ! NULL) 79 { 80 g_OSTsakList[gTaskIndex](); 81 g_OSTsakList[gTaskIndex] NULL; 82 } 83 } 84 } 85 /* USER CODE END 3 */ 86 } ​​​​​​​1.4.1定时器触发 通过两个定时器分别产生5ms与200ms的中断周期性的判断事件的任务标志位当该事件产生时将相关事件的任务加入至主任务循环事件的任务标志组如下 01 //任务使能标值 02 typedef struct 03 { 04 uint32_t UPDATE_DIAL_EN:1; //表盘页面任务使能 05 uint32_t UPDATE_SIX_AXIS_EN:1; //六轴运动任务使能 06 uint32_t UPDATE_WIFI_RSSI_EN:1; //WiFi联网任务使能 07 uint32_t UPDATE_APP_TASK_EN:1; //APP页面任务使能 08 uint32_t UPDATE_CHIPPAGE:1; //系统信息任务使能 09 uint32_t UPDATE_HEALTHPAGE:1; //健康任务使能 10 uint32_t UPDATE_APPPAGE:1; //应用界面使能 11 uint32_t UPDATE_INFOPAGE:1; //INFO界面使能 12 uint32_t UPDATE_WIFIPAGE:1; //WiFi界面使能 13 uint32_t UPDATE_SETTINGPAGE:1; //Setting界面使能 14 uint32_t UPDATE_MOTORPAGE:1; //Motor界面使能 15 uint32_t UPDATE_BATTERYPAGE:1; //Battery界面使能 16 uint32_t UPDATE_SPORTPAGE:1; //Sport界面使能 17 uint32_t UPDATE_ALIPAYPAGE:1; //Alipay界面使能 18 uint32_t :18; 19 }gTask_MarkEN; 采用STM32的HAL库开发在定时器回调函数中判断事件的任务标志位并加入至主循环任务定时器回调函数如下 01 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 02 { 03 static uint8_t p_Time16Cnt 0,p_Time17Cnt 0; 04 /******************************************************************/ 05 //定时器16进行5ms任务中断 06 if (htim-Instance htim16.Instance) 07 { 08 p_Time16Cnt; 09 // 10 if(!(p_Time16Cnt % 4)) //20ms(50Hz)进行触发刷新 11 { 12 touchgfx_signalVSynTimer(); //touchgfx用户接口 13 } 14 //五项按键读取 15 if(!(p_Time16Cnt % 20)) //100ms进行一次窗口更新 16 { 17 if(gTaskStateBit.TouchPress 0) //更新五向键数据 18 { 19 g_OSTsakList[eUPDATE_FIVEKEY] Update_FiveKey_Value; 20 } 21 } 22 //1000ms运行一次系统运行指示灯 23 if(!(p_Time16Cnt % 200)) 24 { 25 p_Time16Cnt 0; 26 HAL_GPIO_TogglePin(BLUE_LED_GPIO_Port,BLUE_LED_Pin); 27 } 28 } 29 /******************************************************************/ 30 //定时器17进行100ms任务中断 31 if (htim-Instance htim17.Instance) 32 { 33 p_Time17Cnt; 34 //周期为200ms任务 35 if(!(p_Time17Cnt % 2)) //200ms进行一次下列代码 36 { 37 if((gTaskEnMark.UPDATE_DIAL_EN || gTaskEnMark.UPDATE_SIX_AXIS_EN) 38 (gTaskStateBit.TouchPress 0)) 39 {//欧拉角更新 40 g_OSTsakList[eUPDATE_SIX_AXIS] Update_EulerAngle; 41 } 42 } 43 //周期为300ms任务 44 if(!(p_Time17Cnt % 3)) //300ms进行一次下列代码 45 { 46 if(gTaskEnMark.UPDATE_WIFI_RSSI_EN) //获取wifi连接的RSSI值 47 g_OSTsakList[eUPDATE_WIFI_RSSI] ESP8266_RSSI_Task; 48 } 49 //周期为500ms任务 50 if(!(p_Time17Cnt % 5)) //500ms进行一次下列代码 51 { 52 if(gTaskEnMark.UPDATE_CHIPPAGE) //系统信息更新 53 g_OSTsakList[eUPDATE_CHIPINFO] Update_ChipInfo; 54 } 55 //周期为1000ms任务 56 if(!(p_Time17Cnt % 10)) //1s进行一次下列代码 57 { 58 if(gTaskEnMark.UPDATE_DIAL_EN (gTaskStateBit.TouchPress 0)) 59 { //系统时间更新 60 g_OSTsakList[eUPDATE_TIME] Update_System_Time; 61 } 62 } 63 //周期为2000ms任务 64 if(!(p_Time17Cnt % 20)) //2s进行一次下列代码 65 { 66 if((gTaskEnMark.UPDATE_DIAL_EN || gTaskEnMark.UPDATE_INFOPAGE) 67 (gTaskStateBit.TouchPress 0)) 68 {//更新电压、电流、温湿度、光照度 69 g_OSTsakList[eUPDATE_DIAL_INFO] Update_DialInfo; 70 } 71 } 72 //周期为3000ms任务 73 if(!(p_Time17Cnt % 30)) //3s进行一次下列代码 74 { 75 //心率任务会阻塞主程序 76 if(gTaskEnMark.UPDATE_HEALTHPAGE) //获取健康信息 77 { 78 g_OSTsakList[eUPDATE_HEART_RATE] Update_HeartRateInfo; 79 } 80 } 81 //周期为10000ms任务 82 if(!(p_Time17Cnt % 100)) //10s进行一次下列代码 83 { 84 p_Time17Cnt 0; 85 } 86 } 87 /******************************************************************/ 88 /* Prevent unused argument(s) compilation warning */ 89 UNUSED(htim); 90 } ​​​​​​​1.4.2 界面事件驱动 在TouchGFX中从Model类执行与应用非UI部分称为后端系统的通信。后端系统是从UI接收事件和将事件输入UI的软件组件 。在TouchGFX中提供一个在每个TouchGFX嘀嗒时间调用一次的函数在该函数中处理需要的通信。以下是将信息输入至UI的软件组件代码 001 void Model::tick() 002 { 003 static uint8_t tickCount 0; //减少数据上传的次数优化界面刷新 004 tickCount; 005 006 #if defined LINK_HARDWARE 007 // 008 if(gSwitchSpace ! 0) gSwitchSpace--; 009 /*************************硬件页面切换************************/ 010 //表盘页面 011 if(gTaskEnMark.UPDATE_DIAL_EN (gTaskStateBit.TouchPress 0) 012 (!gSwitchSpace)) 013 { 014 modelListener-DialPageChange(gFiveKeyFunc); 015 gSwitchSpace 0x0F; //使能切换时间计数 016 } 017 //应用页面 018 if(gTaskEnMark.UPDATE_APPPAGE (gTaskStateBit.TouchPress 0) 019 (!gSwitchSpace)) 020 { 021 modelListener-AppPageChange(gFiveKeyFunc); 022 gSwitchSpace 0x0F; //使能切换时间计数 023 } 024 //六轴页面 025 if(gTaskEnMark.UPDATE_SIX_AXIS_EN (gTaskStateBit.TouchPress 0) 026 (!gSwitchSpace)) 027 { 028 modelListener-SixAxisPageChange(gFiveKeyFunc); 029 gSwitchSpace 0x0F; //使能切换时间计数 030 } 031 //无线页面 032 if(gTaskEnMark.UPDATE_WIFIPAGE (gTaskStateBit.TouchPress 0) 033 (!gSwitchSpace)) 034 { 035 modelListener-WiFiPageChange(gFiveKeyFunc); 036 gSwitchSpace 0x0F; //使能切换时间计数 037 } 038 //设置页面 039 if(gTaskEnMark.UPDATE_SETTINGPAGE (gTaskStateBit.TouchPress 0) 040 (!gSwitchSpace)) 041 { 042 modelListener-SettingPageChange(gFiveKeyFunc); 043 gSwitchSpace 0x0F; //使能切换时间计数 044 } 045 //控制页面 046 if(gTaskEnMark.UPDATE_MOTORPAGE (gTaskStateBit.TouchPress 0) 047 (!gSwitchSpace)) 048 { 049 modelListener-MotorPageChange(gFiveKeyFunc); 050 gSwitchSpace 0x0F; //使能切换时间计数 051 } 052 //信息页面温度、湿度与光强度 053 if(gTaskEnMark.UPDATE_INFOPAGE (gTaskStateBit.TouchPress 0) 054 (!gSwitchSpace)) 055 { 056 modelListener-InfoPageChange(gFiveKeyFunc); 057 gSwitchSpace 0x0F; //使能切换时间计数 058 } 059 //健康页面 060 if(gTaskEnMark.UPDATE_HEALTHPAGE (gTaskStateBit.TouchPress 0) 061 (!gSwitchSpace)) 062 { 063 modelListener-HealthPageChange(gFiveKeyFunc); 064 gSwitchSpace 0x0F; //使能切换时间计数 065 } 066 //Chip页面 067 if(gTaskEnMark.UPDATE_CHIPPAGE (gTaskStateBit.TouchPress 0) 068 (!gSwitchSpace)) 069 { 070 modelListener-ChipPageChange(gFiveKeyFunc); 071 gSwitchSpace 0x0F; //使能切换时间计数 072 } 073 //Battery页面 074 if(gTaskEnMark.UPDATE_BATTERYPAGE (gTaskStateBit.TouchPress 0) 075 (!gSwitchSpace)) 076 { 077 modelListener-BatteryPageChange(gFiveKeyFunc); 078 gSwitchSpace 0x0F; //使能切换时间计数 079 } 080 //Sport页面退出 081 if(gTaskEnMark.UPDATE_SPORTPAGE (gTaskStateBit.TouchPress 0) 082 (!gSwitchSpace)) 083 { 084 modelListener-SportPageExit(gFiveKeyFunc); 085 gSwitchSpace 0x0F; //使能切换时间计数 086 } 087 //Alipay页面退出 088 if(gTaskEnMark.UPDATE_ALIPAYPAGE (gTaskStateBit.TouchPress 0) 089 (!gSwitchSpace)) 090 { 091 modelListener-AlipayPageChange(gFiveKeyFunc); 092 gSwitchSpace 0x0F; //使能切换时间计数 093 } 094 //Sport页面进入 095 if(!HAL_GPIO_ReadPin(USER_KEY_GPIO_Port,USER_KEY_Pin)) 096 { 097 modelListener-SportPageEnter(3); 098 } 099 /***********************更新各类信息***********************/ 100 //更新时间信息,为使表盘页面滑动操作正常在屏幕被点按时不更新数据 101 if(gTaskEnMark.UPDATE_DIAL_EN (gSystemTime.Seconds ! gLastTimeSeconds) 102 (gTaskStateBit.TouchPress 0)) 103 { 104 modelListener-updateDate(gSystemDate.Year,gSystemDate.Month, 104 gSystemDate.Date,gSystemDate.WeekDay); 105 modelListener-updateTime(gSystemTime.Hours, gSystemTime.Minutes, 105 gSystemTime.Seconds); 106 //更新新值 107 gLastTimeSeconds gSystemTime.Seconds; 108 //更新温度/步数/心率 109 modelListener-updateTempStepHeart(gTemRH_Val.Tem,gSportStep,gHeartRate); 110 } 111 //健康监测信息上传 112 if(gTaskEnMark.UPDATE_HEALTHPAGE (gTaskStateBit.TouchPress 0)) 113 { 114 //send samples and calculation result to terminal 115 if(ch_hr_valid || ch_spo2_valid) 116 { 117 modelListener-updateHeartRateInfo(n_heart_rate/4, n_sp02); 118 gHeartRate n_heart_rate/4; //保存心率数据值表盘页面 119 } 120 // 121 if(gTaskStateBit.Max30102) //单次测量完成清除标志 122 { 123 ch_hr_valid 0; 124 ch_spo2_valid0; 125 gTaskStateBit.Max30102 0; 126 } 127 } 128 //更新欧拉角 129 if(gTaskEnMark.UPDATE_SIX_AXIS_EN) //六轴界面活动时上传 130 { 131 modelListener-updateSixAxis(pitch, roll, yaw); 132 } 133 //只有在系统主页时才进行WiFi的RSSI数据读取 134 if((gTaskEnMark.UPDATE_WIFI_RSSI_EN)) 135 { 136 modelListener-updateWiFiRSSI(gWiFiInfo, ao_wifiRSSI.gRSSI); 137 } 138 //更新温湿度信息 139 if(gTaskEnMark.UPDATE_INFOPAGE) //INFO面活动时上传 140 { 141 modelListener-updateInfo(gTemRH_Val.Hum, gTemRH_Val.Tem, gAP3216C_Val.ALS); 142 } 143 //更新芯片温度、参考电压、Vbat 144 if(gTaskEnMark.UPDATE_CHIPPAGE (!(tickCount % 5))) 145 { 146 modelListener-updateChipInfor(gChipTempVal, gVrefVal, gVbatVal); 147 } 148 //更新电压与电流 149 if(gTaskEnMark.UPDATE_BATTERYPAGE)//更新电压与电流 150 { 151 modelListener-updateBatteryPageInfo(gCurrentVal, gVoltageVal); 152 } 153 #else //Designer仿真 154 timeval timenow; 155 gettimeofday(timenow, NULL); 156 //仿真更新时间 157 modelListener-updateTime((timenow.tv_sec / 60 / 60) % 24, 158 (timenow.tv_sec / 60) % 60, 159 timenow.tv_sec % 60); 160 #endif 161 } 以下是用于活动屏幕的产生的任务标志组的设置 001 //风扇操作 002 void Model::turnFanStatus(bool enable) 003 { 004 #if defined LINK_HARDWARE 005 if(enable true)//风扇状态的设置 006 HAL_GPIO_WritePin(EXT_FAN_GPIO_Port,EXT_FAN_Pin,GPIO_PIN_SET); 007 else 008 HAL_GPIO_WritePin(EXT_FAN_GPIO_Port,EXT_FAN_Pin,GPIO_PIN_RESET); 009 #endif 010 } 011 //振动电机操作 012 void Model::setMotorStatus(bool enable) 013 { 014 #if defined LINK_HARDWARE 015 if(enable true)//振动电机状态的设置 016 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_SET); 017 else 018 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_RESET); 019 #endif 020 } 021 //排水操作 022 void Model::drainWaterStatus(bool enable) 023 { 024 #if defined LINK_HARDWARE 025 if(enable true)//振动电机设置 026 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_SET); 027 else 028 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_RESET); 029 #endif 030 } 031 //蜂鸣器操作 032 void Model::setBuzzerStatus(bool enable) 033 { 034 #if defined LINK_HARDWARE 035 if(enable true)//蜂鸣器状态的设置 036 HAL_GPIO_WritePin(RUN_BEEP_GPIO_Port,RUN_BEEP_Pin,GPIO_PIN_SET); 037 else 038 HAL_GPIO_WritePin(RUN_BEEP_GPIO_Port,RUN_BEEP_Pin,GPIO_PIN_RESET); 039 #endif 040 } 041 /*********************gTaskEnMark赋值*************************/ 042 //DialView的任务的状态 043 void Model::DialPageViewTask(bool enable) 044 { 045 #if defined LINK_HARDWARE 046 if(enable true) 047 gTaskEnMark.UPDATE_DIAL_EN 1; //任务使能 048 else 049 gTaskEnMark.UPDATE_DIAL_EN 0; //任务清除 050 #endif 051 } 052 //ApplicationPageView的任务的状态 053 void Model::ApplicationPageViewTask(bool enable) 054 { 055 #if defined LINK_HARDWARE 056 if(enable true) 057 gTaskEnMark.UPDATE_APPPAGE 1; //任务使能 058 else 059 gTaskEnMark.UPDATE_APPPAGE 0; //任务清除 060 #endif 061 } 062 //SixAxisPageView的任务的状态 063 void Model::SixAxisPageViewTask(bool enable) 064 { 065 #if defined LINK_HARDWARE 066 if(enable true) 067 gTaskEnMark.UPDATE_SIX_AXIS_EN 1; //任务使能 068 else 069 gTaskEnMark.UPDATE_SIX_AXIS_EN 0; //任务清除 070 #endif 071 } 072 //InfoPageView任务使能 073 void Model::InfoPageViewTask(bool newStatus) 074 { 075 #if defined LINK_HARDWARE 076 if(newStatus true) 077 gTaskEnMark.UPDATE_INFOPAGE 1; //任务使能 078 else 079 gTaskEnMark.UPDATE_INFOPAGE 0; //任务清除 080 #endif 081 } 082 //ChipPageViewTask的任务的状态 083 void Model::ChipPageViewTask(bool enable) 084 { 085 #if defined LINK_HARDWARE 086 if(enable true) 087 gTaskEnMark.UPDATE_CHIPPAGE 1; //任务使能 088 else 089 gTaskEnMark.UPDATE_CHIPPAGE 0; //任务清除 090 #endif 091 } 092 //设置健康监测任务 093 void Model::HealthPageViewTask(bool newStatus) 094 { 095 #if defined LINK_HARDWARE 096 if(newStatus true) 097 gTaskEnMark.UPDATE_HEALTHPAGE 1; //任务使能 098 else 099 gTaskEnMark.UPDATE_HEALTHPAGE 0; //任务清除 100 #endif 101 } 102 //WiFi连接的任务状态 103 void Model::WiFiLinkTask(bool enable) 104 { 105 #if defined LINK_HARDWARE 106 if(enable true) 107 gTaskEnMark.UPDATE_WIFI_RSSI_EN 1; //任务使能 108 else 109 gTaskEnMark.UPDATE_WIFI_RSSI_EN 0; //任务清除 110 #endif 111 } 112 //WiFi界面的任务状态 113 void Model::WiFiPageViewTask(bool enable) 114 { 115 #if defined LINK_HARDWARE 116 if(enable true) 117 gTaskEnMark.UPDATE_WIFIPAGE 1; //任务使能 118 else 119 gTaskEnMark.UPDATE_WIFIPAGE 0; //任务清除 120 #endif 121 } 122 //Setting界面的任务状态 123 void Model::SettingPageViewTask(bool enable) 124 { 125 #if defined LINK_HARDWARE 126 if(enable true) 127 gTaskEnMark.UPDATE_SETTINGPAGE 1; //任务使能 128 else 129 gTaskEnMark.UPDATE_SETTINGPAGE 0; //任务清除 130 #endif 131 } 132 //Motor界面的任务状态 133 void Model::MotorPageViewTask(bool enable) 134 { 135 #if defined LINK_HARDWARE 136 if(enable true) 137 gTaskEnMark.UPDATE_MOTORPAGE 1; //任务使能 138 else 139 gTaskEnMark.UPDATE_MOTORPAGE 0; //任务清除 140 #endif 141 } 142 //Battery界面的任务状态 143 void Model::BatteryPageViewTask(bool enable) 144 { 145 #if defined LINK_HARDWARE 146 if(enable true) 147 gTaskEnMark.UPDATE_BATTERYPAGE 1; //任务使能 148 else 149 gTaskEnMark.UPDATE_BATTERYPAGE 0; //任务清除 150 #endif 151 } 152 //运动界面的任务状态 153 void Model::SportPageViewTask(bool enable) 154 { 155 #if defined LINK_HARDWARE 156 if(enable true) 157 gTaskEnMark.UPDATE_SPORTPAGE 1; //任务使能 158 else 159 gTaskEnMark.UPDATE_SPORTPAGE 0; //任务清除 160 #endif 161 } 162 //Aliplay界面任务状态 163 void Model::AlipayPageViewTask(bool enable) 164 { 165 #if defined LINK_HARDWARE 166 if(enable true) 167 gTaskEnMark.UPDATE_ALIPAYPAGE 1; //任务使能 168 else 169 gTaskEnMark.UPDATE_ALIPAYPAGE 0; //任务清除 170 #endif 171 }
http://www.pierceye.com/news/106655/

相关文章:

  • 北京网站手机站建设公司手机网站开发常用工具
  • 太原做网站联系方式论坛的网站开发项目
  • drupal 做的网站网站设计与网站制作
  • 我要表白网站在线制作wordpress朗读句子插件
  • 黑龙江建设网官方怎么提高seo关键词排名
  • 拍卖网站开发多少钱十堰秦楚网招聘公告
  • 区域城市分站网站怎么做慈溪网站开发
  • 广州天河网站制作互联网十创业项目
  • 百度是不是只有在自己的网站发布才会被收录完备的常州网站优化
  • 响应式网站开发需要的条件百度信息流广告推广
  • 英文网站建设推广joomla drupal wordpress
  • 什么网站做简历免费下载怎么申请专线访问国际网络
  • 南三环做网站的公司网站开发开题报告计划进度安排
  • 我想建一个网站怎么建微信 网站提成方案点做
  • 苏中建设集团官方网站网络推广文章的方法
  • 月嫂云商城网站建设切换国外ip的软件
  • 沧州网站建设 凯航怎么样建立一个网站
  • 百度网站地图模板在猪八戒上做网站要注意什么
  • 做美工需要哪些网站重庆交通建设集团网站
  • 网站手机端排名怎么做用笔记本做网站
  • 食药监局网站建设方案南京企业自助建站
  • 竹山县住房和城乡建设局网站红豆网桂林论坛
  • 浙江省建设网站专题网站建设的请示
  • php网站好吗红旗渠建设集团网站
  • 网站建设如何不被忽悠做营销怎样才能吸引客户
  • 代写文章兼职济南公交优化
  • 商城开发网站建立公司网站步骤
  • 查询域名注册网站泰国做网站
  • 网站图片描述怎么写社区类网站有哪些
  • 网站竞争对手的选定一般参考什么标准的网站的推广方案的内容有哪些