物联网智慧教室项目(完整版)

news2025/4/20 5:59:00

物联网智慧教室项目(一):智慧教室项目解决方案

一、智慧教室项目设计

(一)环境信息采集控制功能

1、硬件设计
  • 使用STM32开发板模拟灯光控制,报警控制,光照信息采集:
    • 灯光控制通过GPIO控制板载LED
    • 报警控制通过GPIO控制蜂鸣器
    • 光照采集通过ADC采集板载光敏电阻
      在这里插入图片描述
2、 软件设计
  • 控制接口
    • LED打开,关闭
    • Buzer打开,关闭
  • 采集任务
    • 创建采集任务
    • 等待Zigbee串口数据
    • 解析Zigbee串口数据

在这里插入图片描述

(二)人机交互功能

1、硬件设计
  • LCD接口
    • 数据/指令接口,采用STM32 FSMC接口
    • 触摸感应接口,采用STM32 SPI接口
  • 外部扩展内存接口
    • 需要跑嵌入式GUI库,对内存要求比较高
    • 采用STM32 FSMC接口驱动外部SRAM
  • 外部扩展FLASH接口
    • GUI显示中文,需要有中文字库,需要把中文字库放在外部FLASH
    • 采用STM32 SPI接口驱动外部FLASH

在这里插入图片描述

2、 软件设计
  • LCD外设驱动
    • Touch驱动
    • LCD驱动
  • 嵌入式GUI库
    • STemWin移植
    • GUI显示任务
    • Touch检测任务

在这里插入图片描述

(三)WebServer功能

1、硬件设计

WebServer是基于网络通信,需要硬件支持

  • 以太网-WebServer
    • STM32 通过RMII接口驱动以太网外设
  • SD卡- 存储网页文件
    • STM32 通过RMII接口驱动以太网外设

在这里插入图片描述

2、软件设计

Web其实就是浏览器与服务器通过HTTP协议进行网络通信

  • STM32作为Web服务器
    • 通过LwIP驱动网卡,实现HttpServer
    • 通过FatFS驱动SD卡,实现存储网页文件
  • Web网页开发
    • 移植开源网页框架

在这里插入图片描述

二、开发环境搭建

(一)软件环境

STM32

STM32CubeMX
/*
	1.STM32CubeMX 要求版本 5.0.0以上
*/

在这里插入图片描述

/*
	2.STM32Cube MCU Package for STM32F4   要求版本1.23.0
*/	

在这里插入图片描述

MDK-ARM
/*
	MDK-ARM 要求版本5.23.0.0 以上
*/

在这里插入图片描述

/*
	Keil.STM32F4xx_DFP.2.12.0.pack
*/

在这里插入图片描述

zigbee

IAR for 8051
/*
	EW8051-EV-8103-Web
*/

在这里插入图片描述

Zstack
/*
	EW8051-EV-8103-Web
*/

在这里插入图片描述

串口调试工具

在这里插入图片描述

(二)硬件环境

STM32

STM32F407开发板

在这里插入图片描述

STLINK
/*需要安装驱动程序*/

在这里插入图片描述

USB转RS232
/*需安装USB串口驱动*/

在这里插入图片描述

读卡器

在这里插入图片描述

网线

在这里插入图片描述

zigbee

CC2530开发板
/*需安装SmartRF04EB驱动*/

在这里插入图片描述

物联网智慧教室项目(二):智慧教室项目驱动开发

一、环境监控驱动开发

在这里插入图片描述

外设驱动开发流程

Created with Raphaël 2.3.0 原理图分析 数据手册分析 外设配置

(一)时钟&SWD配置

原理图分析

时钟原理图

如下图所示,STM32F407外部高速晶振为25MHz,分别连接到PH0和PH1引脚!

在这里插入图片描述

SWD原理图

如下图所示,STM32F407仿真接口SWD分别连接到PA13和PA14引脚!

在这里插入图片描述

外设配置

时钟外设配置
使能时钟源
  1. 选择RCC外设
  2. 选择高速时钟为外部时钟源
  3. PH0和PH1引脚自动高亮

在这里插入图片描述

配置时钟树
  1. 锁相环时钟源为25MHz外部高速时钟
  2. 高速时钟分频系数配置为25,输出为1MHz
  3. 倍频系数配置为336
  4. 分频系数配置为2,输出为168MHz
  5. 系统时钟源选择PLL
  6. APB1配置为4分频,为42MHz
  7. APB2配置为2分频,为84MHz
    在这里插入图片描述
SWD外设配置
  1. 选择SYS外设
  2. 配置debug接口为串行接口(SWD)
  3. 引脚自动高亮

在这里插入图片描述

(二)串口配置

原理图分析

Zigbee通信接口
  1. J28为Zigbee模块底座,其中Z_W_R和Z_W_T分别Zigbee串行通信接口
  2. Z_W_R和Z_W_T网络连接到J13,通过J13选择连接STM32还是USB转串口,我们选择连接到STM32上的USART1(PA9和PA10)

在这里插入图片描述

在这里插入图片描述

串行调试接口

如下图所示:

  1. 板载两个串行通信接口,串口1连接到USART1,串口2连接到USART3,我们选择USART3
  2. 由于USART3可以用于串口和485通信,我们选择485必须要把CON4和CON5拨到串口通信

在这里插入图片描述

外设配置

USART1配置
  1. 配置PA9和PA10为USART1模式
  2. 打开USART1,配置为异步通信模式

在这里插入图片描述

3.使能NVIC,优先级先默认为0

在这里插入图片描述

USART3配置
  1. 配置PB10和PB11为USART3模式
  2. 打开USART3外设,并配置为异步通信模式

在这里插入图片描述

(三)GPIO配置

原理图分析

  1. PF7 PF8 PF9 PF10 控制板载的D6 D7 D8 D9
  2. PF6控制板载蜂鸣器

在这里插入图片描述

GPIO外设配置

  1. 配置PF6-PF10为输出模式
  2. PF6默认输出低
  3. PF7-PF10默认输出高

在这里插入图片描述

二、人机交互驱动开发

在这里插入图片描述

(一)FSMC

1、SRAM

原理图分析

通过下图所示:

  1. 采用IS61LV51216 SRM 为1MB,其实为了节约成本焊接的为IS61LV25616 为512KB
  2. 占用地址总线为18bit,数据总线为16bit
  3. 内存访问起始地址为0x6800 0000
    在这里插入图片描述
数据手册分析
read周期

地址建立时间 <7ns

数据建立+保持时间 = 7 + 2.5ns = 10ns

在这里插入图片描述

write周期

数据建立周期 = 5 + 3 = 8 + 5ns = 13ns

在这里插入图片描述

FSMC读写周期
read

在这里插入图片描述

write

在这里插入图片描述

外设配置
  1. 打开FSMC外设
  2. 配置FSMC
    1. 选择存储块为NE3
    2. 内存类型为SRAM
    3. 寻址长度为18bit
    4. 数据宽度为16bit
  3. 配置FSMC时序
    1. 地址建立时间为1分频 1/168 = 0.005 = 5ns
    2. 数据建立时间为3分频

在这里插入图片描述

  1. 字节访问使能
    在这里插入图片描述

2、LCD

原理图分析
  1. 如下图所示,LCD采用8080接口,CS片选,D/C命令/数据切换,RD读操作,WR写操作,D[23:0]数据总线

  1. 如下图所示,数据总线D[0:15]连接FSMC总线接口处,RS起始就D/C接口,连接到FSMC地址总线A0,CS片选总线连接到FSMC_NE4上,WR写操作连接FSMC_NWE总线上,RD读操作总线连接到FSMC_NOE上,背光控制连接到PC7上

在这里插入图片描述

  1. 写命令操作0x6C00 0000
  2. 写数据操作0x6C00 0002
数据手册分析
  1. 分析LCD驱动芯片时序图,计算得出地址和数据总线建立时间

    地址保持周期 = 2ns

    数据建立周期 = 12 + 1ns = 15ns

    在这里插入图片描述

外设配置
  1. 打开FSMC外设
  2. 配置FSMC参数
    1. 内存块为NE4
    2. 内存类型为LCD
    3. LCD数据/命令切换映射到A0
    4. 数据宽度为16bit

在这里插入图片描述

  1. 配置PC7为输出模式
    1. 上电默认输出高电平
      在这里插入图片描述

SPI

FLASH

原理图分析
  1. 如下图所示,SPI接口,CS连接到PH2,MISO连接到PB4,MOSI连接到PB5,CLK连接到PA5

在这里插入图片描述

数据手册分析

1.时钟极性及相位

通过时序图分析,SPI CLK 高有效 时钟极性为高

时钟边沿为奇数边沿
在这里插入图片描述

2.SPI速率

满足数据传输速率,最大不能超过33MHz

在这里插入图片描述

外设配置
  1. 配置SPI时钟和数据引脚
  2. 配置SPI为全双工主机模式
  3. 配置SPI参数
    1. 通信速率为系统时钟4分频
    2. 时钟极性为低电平
    3. 相位为奇数边沿

在这里插入图片描述

  1. 配置片选引脚PH2默认输出高

Touch

原理图分析
  1. 如下图所示,SPI接口,CS连接到PG15,MISO连接到PI2,MOSI连接到PI3,SCK连接到PI1
  2. 触摸中断连接到PG7

在这里插入图片描述

数据手册分析
  1. 通过计算TCH+TCL得出SPI通信速率
  2. 通过时序图分析,SPI不工作时为低电平

在这里插入图片描述

  1. 时钟边沿为奇数边沿
  2. 通信速率最小为400ns,大概2Mbit/S左右

在这里插入图片描述

外设配置
  1. 配置SPI时钟和数据引脚
  2. 配置SPI为全双工主机模式
  3. 配置SPI参数
    1. 通信速率为系统时钟32分频
    2. 时钟极性为低电平
    3. 相位为奇数边沿

在这里插入图片描述

  1. 配置SPI片选引脚
    1. 配置PG15为输出模式
    2. 配置PG15上电默认输出高,SPI低电平有效

在这里插入图片描述

三、WebServer驱动开发

在这里插入图片描述

(一)SDIO

原理图分析

根据原理图分析,我们采用SD总线,4bit

配置CLK、DAT0-3
在这里插入图片描述

在这里插入图片描述

外设配置

  1. 打开SDIO外设
  2. 配置SD总线为4bit位宽
  3. 配置DMA接收和发送

在这里插入图片描述

  1. 使能sdio全局中断 重点配置DMA优先级小于SDIO优先级

在这里插入图片描述

(二)ETH

原理图分析

  1. 如下图所示,以太网PHY采用DP83848芯片,通信模式采用RMII接口

  2. 配置MDC、CLK、MDIO、DV、RXD0、RXD1;EN、TXD0、TXD1

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

数据手册分析

Created with Raphaël 2.3.0 配置PHY地址为0x01 配置PHYSTS寄存器地址为0x10 配置Speed Status 掩码为0x0002 配置Duplex Status 掩码为0x0004

PHY地址

在这里插入图片描述

PHY寄存器

BMCR

在这里插入图片描述

BMSR

在这里插入图片描述

PHYSTS

在这里插入图片描述

外设配置

在这里插入图片描述

在这里插入图片描述

四、FreeRTOS配置及任务创建

(一)freeRTOS配置

配置内核定时器

由于我们采用STM32HAL库进行开发,HAL库内部使用systick定时器用于系统延时功能,而FreeRTOS也需要一个定时器用于操作系统内核调度使用, 顾需要修改HAL定时器时钟源

  1. 打开SYS选项
  2. 配置时钟源为TIM1

在这里插入图片描述

配置FreeRTOS内核功能

  • 多数功能在后续程序设计中,需要根据具体功能,进行配置
  • 前期只需要配置动态内存空间和创建开始任务就可以
配置动态内存分配空间
  1. 采用FreeRTOS动态内存分配,开发效率高!顾我们程序内存使用,多数使用动态内存分配方式,分配动态内存总空间为23k=23552byte
    2. 使能FreeRTOS功能
    3. 分配内存空间为40960

在这里插入图片描述

(二)任务创建

任务及优先级划分

Created with Raphaël 2.3.0 WebServer 高 Touch Zigbee GUI 低

任务堆栈划分

Created with Raphaël 2.3.0 WebServer 16K Touch 4K Zigbee 1K GUI 8K

1、中断优先级分配

在这里插入图片描述

2、printf重定向

  1. 在main.c文件内添加fputc函数,采用USART3作为调试接口
int fputc(int ch, FILE *p)
{
	while(!(USART1->SR & (1<<7)));
	
	USART1->DR = ch;
	
	return ch;
}
  1. 在touch任务内打印启动信息在这里插入图片描述在这里插入图片描述

五、lwIP配置及网卡驱动

(一)lwIP配置

1、IP组网配置

  1. 使能lwIP
  2. 关闭DHCP服务
  3. 配置IP地址信息

在这里插入图片描述

2、lwIP参数配置

  1. 分配10k内存空间
  2. 使能链路检测回调功能

在这里插入图片描述

(二)ping测试

配置笔记本的网段和STM32开发板的网段一致

在这里插入图片描述

在这里插入图片描述

以太网断线检测

ethernetif.c
/* USER CODE BEGIN 8 */
/**
  * @brief  This function notify user about link status changement.
  * @param  netif: the network interface
  * @retval None
  */
__weak void ethernetif_notify_conn_changed(struct netif *netif)
{
  /* NOTE : This is function could be implemented in user file 
            when the callback is needed,
  */
	
	if(netif_is_link_up(netif)){
		
		printf("netif link is up\r\n");
		if(!netif_is_up(netif)){
			netif_set_up(netif);
			printf("netif is up\r\n");
		}
	}else{
		printf("netif link is down\r\n");
		
	}

}

两种情况,一种是设备通电时断开网线,一种是网线没插,设备通电

  • 第一种, 断开网线之后再插入,通过ping命令进行检测
  • 第二种,断开网线,设备通电,当设备正常工作后,再插入网线,通过ping命令进行检测。

物联网智慧教室项目(三):嵌入式文件系统FatFS

一、FatFS介绍及STM32集成

(一)FatFS介绍

官方网站(可边翻译成中文对照学习)

http://elm-chan.org/fsw/ff/00index_e.html

在这里插入图片描述

应用接口

在这里插入图片描述

介质访问接口

在这里插入图片描述

相关资源

在这里插入图片描述

(二)STM32Cube集成FatFS

FatFs 中间件模块架构

在这里插入图片描述

FatFS配置

  1. 打开FatFS
  2. 使能磁盘为SD卡
  3. 配置中文编码
  4. 配置命名空间为HEAP

在这里插入图片描述

  1. 增大C库堆空间

在这里插入图片描述

FatFS示例代码

uint8_t u8chr[] = "hello";
uint32_t u32Wbytes;

/* USER CODE END Variables */    

void MX_FATFS_Init(void) 
{
  /*## FatFS: Link the SD driver ###########################*/
  retSD = FATFS_LinkDriver(&SD_Driver, SDPath);
  
  

  /* USER CODE BEGIN Init */
  
  if(f_mount(&SDFatFS,SDPath,1) == FR_OK)
  {
    if(f_open(&SDFile,(const char*)"fatfs.txt",FA_CREATE_ALWAYS|FA_WRITE) == FR_OK)
    {
      if(f_write(&SDFile,u8chr,sizeof(u8chr),&u32Wbytes) == FR_OK)
      {
        f_close(&SDFile);
      
      }
    
    }
  
  }
  
  
  /* additional user code for init */     
  /* USER CODE END Init */
}

二、FatFS应用编程上

(一)FatFS提供的应用接口

在这里插入图片描述

(二)API学习方法

学习步骤

Created with Raphaël 2.3.0 函数原型分析 典型代码案例阅读 应用编程

项目当中用到的API

f_mkfs //物理磁盘的格式化
f_mount
f_open
f_read
f_write
f_size
f_close

(三)API分析

f_mkfs

FRESULT f_mkfs (
  const TCHAR* path,  /* [IN] Logical drive number */
  BYTE  opt,          /* [IN] Format options */
  DWORD au,           /* [IN] Size of the allocation unit */
  void* work,         /* [-]  Working buffer */
  UINT len            /* [IN] Size of working buffer */
);

f_mount

FRESULT f_mount (
  FATFS*       fs,    /* [IN] File system object */
  const TCHAR* path,  /* [IN] Logical drive number */
  BYTE         opt    /* [IN] Initialization option */
);

f_open

FRESULT f_open (
  FIL* fp,           /* [OUT] Pointer to the file object structure */
  const TCHAR* path, /* [IN] File name */
  BYTE mode          /* [IN] Mode flags */
);

f_close

FRESULT f_close (
  FIL* fp     /* [IN] Pointer to the file object */
);

f_read

FRESULT f_read (
  FIL* fp,     /* [IN] File object */
  void* buff,  /* [OUT] Buffer to store read data */
  UINT btr,    /* [IN] Number of bytes to read */
  UINT* br     /* [OUT] Number of bytes read */
);

f_write

FRESULT f_write (
  FIL* fp,          /* [IN] Pointer to the file object structure */
  const void* buff, /* [IN] Pointer to the data to be written */
  UINT btw,         /* [IN] Number of bytes to write */
  UINT* bw          /* [OUT] Pointer to the variable to return number of bytes written */
);

f_size

FSIZE_t f_size (
  FIL* fp   /* [IN] File object */
);

三、FatFS应用编程下

(一)实际文件系统应用案例分析

//远程终端单元( Remote Terminal Unit,RTU)

在这里插入图片描述

(二)怎么存储历史数据呢

历史数据的目的是什么

在这里插入图片描述

CSV格式文件

在这里插入图片描述

(三)历史数据存储功能实现

Created with Raphaël 2.3.0 创建文件 格式化文件流 文件写入

(四)代码

	if(f_open(&SDFile,(const char*)"Sensor.csv",FA_CREATE_ALWAYS|FA_WRITE) == FR_OK)
	{
		//格式化文件流
		//创建表头
		sprintf(SensorBuff,"序号,温度,适度,光照\r\n");
		f_write(&SDFile,SensorBuff,strlen(SensorBuff),&u32Wbytes);
		
		//循环写入表项
		for(int i; i < 10; i++)
		{
			sprintf(SensorBuff,"%d,%d,%d,%d\r\n",i + 1, i + 20, i + 30, i + 40);
			f_write(&SDFile,SensorBuff,strlen(SensorBuff),&u32Wbytes);
			//刷新到文件中
			//f_sync(&SDFile);
		}
		//关闭文件,缓存写入文件内
		f_close(&SDFile);
	}
	

四、FatFS底层实现

在这里插入图片描述

通用底层驱动API

在这里插入图片描述

驱动包含哪些文件

ff_gen_drv.c

//FatFS 提供的通用驱动文件的实现
/**
  * @brief  Disk IO Driver structure definition
  */
typedef struct
{
  DSTATUS (*disk_initialize) (BYTE);                     /*!< Initialize Disk Drive                     */
  DSTATUS (*disk_status)     (BYTE);                     /*!< Get Disk Status                           */
  DRESULT (*disk_read)       (BYTE, BYTE*, DWORD, UINT);       /*!< Read Sector(s)                            */
#if _USE_WRITE == 1
  DRESULT (*disk_write)      (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0       */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT (*disk_ioctl)      (BYTE, BYTE, void*);              /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */

}Diskio_drvTypeDef;
/**
  * @brief  Global Disk IO Drivers structure definition
  */
typedef struct
{
  uint8_t                 is_initialized[_VOLUMES];//磁盘是否初始化
  const Diskio_drvTypeDef *drv[_VOLUMES];//磁盘的驱动
  uint8_t                 lun[_VOLUMES];//磁盘的编号
  volatile uint8_t        nbr;

}Disk_drvTypeDef;

sd_diskio.c

//针对SD底层驱动实现,封装成为通用的底层驱动API
//如果使能freeRTOS,在read和Write里面,会用到操作系统的消息队列

bsp_driver_sd.c

//HAL库的二次封装,把所有基于SD卡的操作都在bsp_driver_sd实现

驱动装载

Created with Raphaël 2.3.0 MX_FATFS_Init FATFS_LinkDriver FATFS_LinkDriverEx
/**
  * @brief  Links a compatible diskio driver/lun id and increments the number of active
  *         linked drivers.
  * @note   The number of linked drivers (volumes) is up to 10 due to FatFs limits.
  * @param  drv: pointer to the disk IO Driver structure
  * @param  path: pointer to the logical drive path
  * @param  lun : only used for USB Key Disk to add multi-lun management
            else the parameter must be equal to 0
  * @retval Returns 0 in case of success, otherwise 1.
  */
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)
{
  uint8_t ret = 1;
  uint8_t DiskNum = 0;
  //判断是否超出了fatfs最大的卷数量
  if(disk.nbr < _VOLUMES)
  {
    //未初始化
    disk.is_initialized[disk.nbr] = 0;
    //把驱动进行链接
    disk.drv[disk.nbr] = drv;
    disk.lun[disk.nbr] = lun;
    DiskNum = disk.nbr++;
    path[0] = DiskNum + '0';  //"1:/"
    path[1] = ':';
    path[2] = '/';
    path[3] = 0;
    ret = 0;
  }

  return ret;
}

物联网智慧教室项目(四):emWin图形界面库

一、emWin移植上

LCD驱动程序

#ifndef __LCD_H__
#define __LCD_H__   
#include "stm32f4xx_hal.h"
void lcd_clear(uint16_t Color);
void lcd_init(void);
void write_data_Prepare(void);
unsigned short lcd_read_gram(unsigned int x,unsigned int y);
void LCD_DrawPoint(uint16_t xsta, uint16_t ysta, uint16_t color);
void LCD_ShowString(uint16_t x0, uint16_t y0, uint8_t *pcStr, uint16_t PenColor, uint16_t BackColor);
void LCD_Fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t colour);
#endif

STemWin结构框架

在这里插入图片描述

(一)CRC开启

在这里插入图片描述

(二)SRAM 写操作要使能

在这里插入图片描述

(三)获取STemWin源码文件

STemWin默认在STM32CUBEMX文档下

例如C:\Users\Think\STM32Cube\Repository\STM32Cube_FW_F4_V1.24.1\Middlewares\ST\STemWin

在这里插入图片描述

(四)emWin移植到项目工程

  1. 复制STemWin源码到项目工程中
    工程目录:SmartClassRoom\Middlewares\Third_Party\STemWin

  2. 在keil工程中添加相关文件

    1. 新建工作组:Middlewares/STemWin
      在这里插入图片描述

    2. 添加需要编译的C和库文件

    在这里插入图片描述

文件名称文件描述
GUI_X_OS.cOS支持文件,不需要修改
GUIConf.cGUI配置文件,主要用于GUI内存块初始化
GUIDRV_Template.cGUI驱动模块,主要针对LCD操作接口
LCDConf_FlexColor_Template.cGUI显示配置文件,主要用于LCD参数配置,初始化
GUI_X_Touch_Analog.c需要自己单独定义,用于触摸笔驱动
STemWin_CM4_OS_wc16_ot.a基于Cortex-M4驱动库,STemWin源码不开放
  1. 修改库文件格式(keil默认不识别.a文件格式,需要我们手动配置)

在这里插入图片描述

(五)移植lcd和touch驱动文件

  1. 添加lcd.c和Touch.c到Src目录下

在这里插入图片描述

  1. 添加lcd.h和Touch.h到Inc目录下
    在这里插入图片描述

二、emWin移植下

(一)emWin LCD驱动适配

修改GUIConf.c

#include "GUI.h"

/*********************************************************************
*
*       Defines
*
**********************************************************************
*/
//
// Define the available number of bytes available for the GUI
//
#define GUI_NUMBYTES    (512*1024)		 //定义外部存储器大小
#define GUI_BLOCKSUZE   (0X80)			//定义最小内存库操作大小
#define SRAM_BANK_ADDR  ((U32)0x68000000)	//定义外部存储器首地址

/*********************************************************************
*
*       Public code
*
**********************************************************************
*/
/*********************************************************************
*
*       GUI_X_Config
*
* Purpose:
*   Called during the initialization process in order to set up the
*   available memory for the GUI.
*/
void GUI_X_Config(void) {
  //
  // 32 bit aligned memory area
  //
	volatile U32* aMemory = (volatile U32*)(SRAM_BANK_ADDR);
  //
  // Assign memory to emWin
  //分配GUI存储器首地址及最小操作内存块大小
  GUI_ALLOC_AssignMemory((void *)aMemory, GUI_NUMBYTES);
  GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSUZE);
  //
  // Set default font
  //
  GUI_SetDefaultFont(GUI_FONT_32_1);
}

修改GUIDRV_Template.c

只需要完成画点和读取点操作即可

/*********************************************************************
*
*       _SetPixelIndex
*
* Purpose:
*   Sets the index of the given pixel. The upper layers
*   calling this routine make sure that the coordinates are in range, so
*   that no check on the parameters needs to be performed.
*/
static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int PixelIndex) {
    //
    // Convert logical into physical coordinates (Dep. on LCDConf.h)
    //
    #if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
      int xPhys, yPhys;

      xPhys = LOG2PHYS_X(x, y);
      yPhys = LOG2PHYS_Y(x, y);
    #else
      #define xPhys x
      #define yPhys y
    #endif
    GUI_USE_PARA(pDevice);
    GUI_USE_PARA(x);
    GUI_USE_PARA(y);
    GUI_USE_PARA(PixelIndex);
    {
      //
      // Write into hardware ... Adapt to your system
      //添加lcd画点接口
      LCD_DrawPoint(x,y,PixelIndex);
      // TBD by customer...
      //
    }
    #if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
      #undef xPhys
      #undef yPhys
    #endif
}
/*********************************************************************
*
*       _GetPixelIndex
*
* Purpose:
*   Returns the index of the given pixel. The upper layers
*   calling this routine make sure that the coordinates are in range, so
*   that no check on the parameters needs to be performed.
*/
static unsigned int _GetPixelIndex(GUI_DEVICE * pDevice, int x, int y) {
  unsigned int PixelIndex;
    //
    // Convert logical into physical coordinates (Dep. on LCDConf.h)
    //
    #if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
      int xPhys, yPhys;

      xPhys = LOG2PHYS_X(x, y);
      yPhys = LOG2PHYS_Y(x, y);
    #else
      #define xPhys x
      #define yPhys y
    #endif
    GUI_USE_PARA(pDevice);
    GUI_USE_PARA(x);
    GUI_USE_PARA(y);
    {
      //
      // Write into hardware ... Adapt to your system
      //添加lcd读取点接口
      PixelIndex = lcd_read_gram(x,y);
      
      // TBD by customer...
      //
      PixelIndex = 0;
    }
    #if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
      #undef xPhys
      #undef yPhys
    #endif
  return PixelIndex;
}

修改LCDConf_FlexColor_Template.c

  1. 定义显示尺寸 480*272
  2. 定义触摸笔X,Y AD测量值(需要自己测量获得)
  3. 添加触摸笔校准函数
  4. 添加lcd初始化函数
#include "GUI.h"
#include "GUIDRV_FlexColor.h"
#include "lcd.h"

/*********************************************************************
*
*       Layer configuration (to be modified)
*
**********************************************************************
*/

//
// Physical display size
//
#define XSIZE_PHYS  480 //  屏幕X坐标长度
#define YSIZE_PHYS  272 //  屏幕Y坐标长度


#define GUI_TOUCH_AD_Y_TOP 170		// 屏幕X0点坐标AD值
#define GUI_TOUCH_AD_Y_BOTTOM 1900	// 屏幕X480点坐标AD值
#define GUI_TOUCH_AD_X_LEFT 100		// 屏幕Y0点坐标AD值
#define GUI_TOUCH_AD_X_RIGHT 1930	// 屏幕Y272点坐标AD值

/*********************************************************************
*
*       Configuration checking
*
**********************************************************************
*/
#ifndef   VXSIZE_PHYS
  #define VXSIZE_PHYS XSIZE_PHYS
#endif
#ifndef   VYSIZE_PHYS
  #define VYSIZE_PHYS YSIZE_PHYS
#endif
#ifndef   XSIZE_PHYS
  #error Physical X size of display is not defined!
#endif
#ifndef   YSIZE_PHYS
  #error Physical Y size of display is not defined!
#endif
#ifndef   GUICC_565
  #error Color conversion not defined!
#endif
#ifndef   GUIDRV_FLEXCOLOR
  #error No display driver defined!
#endif

/*********************************************************************
*
*       Public functions
*
**********************************************************************
*/
/*********************************************************************
*
*       LCD_X_Config
*
* Function description:
*   Called during the initialization process in order to set up the
*   display driver configuration.
*
*/
void LCD_X_Config(void) {
  //
  // 配置GUI LCD驱动以及颜色显示方式
  //
  GUI_DEVICE_CreateAndLink(&GUIDRV_Template_API, GUICC_M565, 0, 0);
  //
  // 显示尺寸配置
  //
  LCD_SetSizeEx (0, XSIZE_PHYS , YSIZE_PHYS);
  LCD_SetVSizeEx(0, VXSIZE_PHYS, VYSIZE_PHYS);
	
  //触摸笔校准
  GUI_TOUCH_Calibrate(GUI_COORD_X, 0, 480, GUI_TOUCH_AD_X_LEFT , GUI_TOUCH_AD_X_RIGHT);
  GUI_TOUCH_Calibrate(GUI_COORD_Y, 0, 272, GUI_TOUCH_AD_Y_TOP, GUI_TOUCH_AD_Y_BOTTOM);
  //
  // Orientation
  //
  //
  // Set controller and operation mode
  //
}

/*********************************************************************
*
*       LCD_X_DisplayDriver
*
* Function description:
*   This function is called by the display driver for several purposes.
*   To support the according task the routine needs to be adapted to
*   the display controller. Please note that the commands marked with
*   'optional' are not cogently required and should only be adapted if
*   the display controller supports these features.
*
* Parameter:
*   LayerIndex - Index of layer to be configured
*   Cmd        - Please refer to the details in the switch statement below
*   pData      - Pointer to a LCD_X_DATA structure
*
* Return Value:
*   < -1 - Error
*     -1 - Command not handled
*      0 - Ok
*/
int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void * pData) {
  int r;
  (void) LayerIndex;
  (void) pData;
  
  switch (Cmd) {
  case LCD_X_INITCONTROLLER: {
    //
    // Called during the initialization process in order to set up the
    // display controller and put it into operation. If the display
    // controller is not initialized by any external routine this needs
    // to be adapted by the customer...
    //
    // ...
		//添加lcd初始化
		lcd_init();
    return 0;
  }
  default:
    r = -1;
  }
  return r;
}

添加GUI_X_Touch_Analog.c

#include "GUI.h"
#include "Touch.h"


void GUI_TOUCH_X_ActivateX(void) 
{

}

void GUI_TOUCH_X_ActivateY(void)
{

}

//获取X坐标AD值
int  GUI_TOUCH_X_MeasureX(void) 
{
	return XPT_Read_XY(CMD_RDX);
}

//获取Y坐标AD值
int  GUI_TOUCH_X_MeasureY(void) 
{	
	return XPT_Read_XY(CMD_RDY);
}

(二)emWin测试程序编写

测试程序编写

/* USER CODE BEGIN Header_Touch_Task */
/**
  * @brief  Function implementing the TouchTask thread.
  * @param  argument: Not used 
  * @retval None
  */
/* USER CODE END Header_Touch_Task */
void Touch_Task(void const * argument)
{

	GUI_PID_STATE State;    
                 
//  /* init code for FATFS */
//  MX_FATFS_Init();

//  /* init code for LWIP */
//  MX_LWIP_Init();


  GUI_Init();
  GUI_SetBkColor(GUI_BLUE);
  GUI_SetFont(GUI_FONT_32_1);
  GUI_SetColor(GUI_YELLOW);
  GUI_Clear();	
  /* Infinite loop */
  for(;;)
  {			
  		//执行触摸笔检测
		GUI_TOUCH_Exec();	
        //获取触摸笔状态值
		GUI_TOUCH_GetState(&State);
		//是否按下
		if(State.Pressed){
			//打印触摸笔坐标信息
			GUI_DispStringAt("X:",0,0);
			GUI_DispDecAt(State.x,32,0,4);
			GUI_DispStringAt("Y:",0,24);
			GUI_DispDecAt(State.y,32,24,4);			
		}
		osDelay(10);
	}
  /* USER CODE END Touch_Task */
}

测试结果

当触摸笔按下时,显示X(0-480)和Y(0-272)坐标信息

在这里插入图片描述

三、emWin开发环境搭建

开发环境压缩包请自提:链接:https://pan.baidu.com/s/12lO9r7CG43mPEpcIbQ3fug?pwd=6pqm

提取码:6pqm

开发环境介绍

CodeBlocks

在这里插入图片描述

GUIBuilder

在这里插入图片描述

Simulation(模拟器)

在这里插入图片描述

emwin Simulation (模拟器)

仿真模拟器是在window开发环境下的C工程,可以通过VC6或者codeblockd IDE环境下进行开发仿真

  1. 下载地址
    https://www.segger.com/downloads/emwin/

根据自己使用的STemwin库,进行下载,我们采用V5.44版本

在这里插入图片描述

  1. 添加工程到codeblockd
    1. 打开外部工程
    2. 选择.cbp文件

在这里插入图片描述

  1. 添加工程到codeblockd
    1. 点击编译运行按钮
    2. 生成模拟器

在这里插入图片描述

GUIBuilder 制作界面

Created with Raphaël 2.3.0 放置窗口 放置按钮 放置文本 生成代码

在这里插入图片描述

界面添加到模拟器

修改模拟器原始工程

  1. 把无用的代码移除工程

在这里插入图片描述

  1. 添加WindowDLG.c到工程中
    1. 首先复制WindowDLG.c到SeggerEval_WIN32_MSVC_MinGW_GUI_V544\Application目录下

    2. 添加WindowDLG.c到工程中

    3. 选择Application工程目录,新建mainTask.c
      在这里插入图片描述

    4. 在mainTask.c添加代码

#include "dialog.h"//包含window对话框 头文件
void MainTask(void)

{

    GUI_Init();                     //初始化emWin
    CreateWindow();                 //创建窗体,父窗体是桌面背景
    while(1) {GUI_Delay(20);}       //调用GUI_Delay函数延时20MS(最终目的是调用GUI_Exec()函数)

}

四、emWin运行原理分析

emWin使用说明书(第三点中压缩包包含)

在这里插入图片描述

emWin初始化

在这里插入图片描述

执行模型

在这里插入图片描述

单任务使用注意事项

我们在操作系统下使用emwin,必须要创建一个任务,用来调用emWin函数,并且官方说,此任务的优先级配置为最低
    
 emWin函数指的是什么???

在这里插入图片描述

时间调度

在这里插入图片描述
在这里插入图片描述

指针输入设备

在这里插入图片描述

五、emWin应用编程方法

GUIBuilder

在这里插入图片描述

对话框

在这里插入图片描述

资源表

资源:我们这个对话框内部的小工具(小窗口)------- window 按钮 文本 图片

在这里插入图片描述

窗口管理器

在这里插入图片描述

窗口消息

在这里插入图片描述

窗口对象

在这里插入图片描述

物联网智慧教室项目(五):人机交互功能开发(模拟器)

一、界面构思

(一)原型分析

1、主界面

主界面提供了,整个系统的交互接口,也就是说,我们都有哪些需要提供给用户展示的
1、数据展示功能
    1.1、温度
    1.2、湿度
    1.3、光照
2、控制操作功能
	2.1、灯光
    2.2、风扇
    2.3、报警
    
分析:
    对用户来说,需要点击相应的图标下,就可以进入相关的界面,很简单,我使用button小工具进行实现,其实就是在主界面创建6个button小工具

在这里插入图片描述

2、子界面

控制界面
控制界面应该有什么?
1、界面的标识--- 要给用户指示这个界面有什么用
    使用text小工具就可以实现
2、控制操作
    开状态-当用户点击图标,灯泡点亮
    关状态-当用户点击图片,灯泡熄灭
3、返回主菜单
    当用户点击主菜单按钮,返回主界面
    
 控制和返回主界面都用button实现即可
    

在这里插入图片描述

数据界面
控制界面应该有什么?
1、界面的标识--- 要给用户指示这个界面有什么用
    使用text小工具就可以实现
2、数据图标展示
 	显示一个图片就可以,用图片小工具
3、返回主菜单
    当用户点击主菜单按钮,返回主界面,用button小工具

在这里插入图片描述

(二)界面构思

1、主界面

在这里插入图片描述

2、传感器数据展示界面

在这里插入图片描述

3、控制设备界面

在这里插入图片描述

(三)素材设计

1、主界面素材

在这里插入图片描述

2、子界面素材

在这里插入图片描述

注意事项

图片最终是用过代码,进行展示的
    我们必须针对图片命名时候,有规范,有要求
1、主界面命名
    MainAlarm
    MainLed
2、子界面
    SubAlarmOpen
    SubAlarmClose
    SubHum

二、交互设计

(一)窗口初始化

业务流程

Created with Raphaël 2.3.0 设置窗口背景色 设置按钮位图

窗口初始化函数接口

句柄的概念

句柄:在C语言里面,它其实就是一个指针变量

在这里插入图片描述

WINDOW_SetBkColor

在这里插入图片描述

WM_GetDialogItem

在这里插入图片描述

BUTTON_SetBitmap

在这里插入图片描述

(二)按键处理

Created with Raphaël 2.3.0 子窗口消息通知 获取子窗口ID 获取子窗口消息 子窗口消息处理

按键处理接口

WM_NOTIFY_PARENT

在这里插入图片描述

按钮通知代码

在这里插入图片描述

(三)界面切换

Created with Raphaël 2.3.0 结束当前对话框 创建需要切换的对话框
界面切换接口
GUI_EndDialog

在这里插入图片描述

GUI_CreateDialogBox

在这里插入图片描述

三、GUIBuilder创建代码框架

GUIBuilder创建主界面

1、创建window小工具
    1.1、设置window x y 尺寸 472 280
    1.2、设置背景色 2 33 79 RGB
2、创建6个button小工具
    2.1、设置button尺寸为 100 100 
    2.2、button布局
  /*
  { WINDOW_CreateIndirect, "", ID_WINDOW_0, 0, 0, 472, 280, 0, 0x0, 0 },
  { BUTTON_CreateIndirect, "", ID_BUTTON_0, 43, 30, 100, 100, 0, 0x0, 0 },
  { BUTTON_CreateIndirect, "", ID_BUTTON_1, 43, 150, 100, 100, 0, 0x0, 0 },
  { BUTTON_CreateIndirect, "", ID_BUTTON_2, 186, 150, 100, 100, 0, 0x0, 0 },
  { BUTTON_CreateIndirect, "", ID_BUTTON_3, 329, 150, 100, 100, 0, 0x0, 0 },
  { BUTTON_CreateIndirect, "", ID_BUTTON_4, 329, 30, 100, 100, 0, 0x0, 0 },
  { BUTTON_CreateIndirect, "", ID_BUTTON_5, 186, 30, 100, 100, 0, 0x0, 0 },
  */

在这里插入图片描述

添加代码到模拟器工程
修改窗口创建函数名称及文件名称

在这里插入图片描述

代码添加到工程

在这里插入图片描述

模拟器运行

在这里插入图片描述

创建温度展示界面

在这里插入图片描述

在这里插入图片描述

创建湿度展示界面

在这里插入图片描述

创建光照展示界面

在这里插入图片描述

创建风扇控制界面

在这里插入图片描述

创建LED控制界面

在这里插入图片描述

创建报警控制界面

在这里插入图片描述

四、BmpCvtST生成图片流

将主界面、子界面图片通过“位图转换工具”转换成c语言文件,然后复制到SeggerEval_WIN32_MSVC_MinGW_GUI_V544文件下的Application文件夹中。

位图转换工具及界面用到的图片素材:
链接:https://pan.baidu.com/s/1KnK-yzwmuKXJQRIgQmIMDQ?pwd=8hyn
提取码:8hyn
在这里插入图片描述
在这里插入图片描述

(一)主界面添加图片流

// USER START (Optionally insert additional static data)
extern GUI_CONST_STORAGE GUI_BITMAP bmMainAlarm;
extern GUI_CONST_STORAGE GUI_BITMAP bmMainFan;
extern GUI_CONST_STORAGE GUI_BITMAP bmMainHum;
extern GUI_CONST_STORAGE GUI_BITMAP bmMainLed;
extern GUI_CONST_STORAGE GUI_BITMAP bmMainLight;
extern GUI_CONST_STORAGE GUI_BITMAP bmMainTemp;
// USER END
    // USER START (Optionally insert additional code for further widget initialization)
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmMainAlarm);
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_1);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmMainFan);
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_2);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmMainHum);
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_3);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmMainLed);
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_4);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmMainLight);
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_5);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmMainTemp);
    // USER END

(二)子界面添加图片流

以HumDLG(湿度子界面)为例,其余五个子界面操作除了命名得修改一下,其余一致,如下:

// USER START (Optionally insert additional static data)
extern GUI_CONST_STORAGE GUI_BITMAP bmSubHome;
extern GUI_CONST_STORAGE GUI_BITMAP bmSubHum;
// USER END
    // USER START (Optionally insert additional code for further widget initialization)
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmSubHome);
    // USER END

五、界面切换功能实现

功能优化

设置子界面背景色为 8 20 44 16进制 2C 14 08
//TEXT_SetBkColor(hItem, GUI_MAKE_COLOR(0x002C1408));

(一)界面切换功能实现

1、主界面切换到子界面

MainDLG.c(以按键0 Alarm为例,其余五个按键操作一致):

case ID_BUTTON_0: // Notifications sent by ''
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        // USER START (Optionally insert code for reacting on notification message)
        // USER END
        break;
      case WM_NOTIFICATION_RELEASED:
        // USER START (Optionally insert code for reacting on notification message)
        GUI_EndDialog(pMsg->hWin, 0);
        AlarmCreate();
        // USER END
        break;
      // USER START (Optionally insert additional code for further notification handling)
      // USER END
      }
      break;

2、子界面
a.控制设备(Alarm、Fan、Led,即需要控制开关的),以Alarm为例:
PS:注意是在按键1写入代码,不是按键0

case ID_BUTTON_1: // Notifications sent by ''
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        // USER START (Optionally insert code for reacting on notification message)
        // USER END
        break;
      case WM_NOTIFICATION_RELEASED:
        // USER START (Optionally insert code for reacting on notification message)
        GUI_EndDialog(pMsg->hWin, 0);
        MainCreate();
        // USER END
        break;
      // USER START (Optionally insert additional code for further notification handling)
      // USER END
      }
      break;

b.传感器(Hum、Light、Temp),以Hum为例

    case ID_BUTTON_0: // Notifications sent by ''
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        // USER START (Optionally insert code for reacting on notification message)
        // USER END
        break;
      case WM_NOTIFICATION_RELEASED:
        // USER START (Optionally insert code for reacting on notification message)
        GUI_EndDialog(pMsg->hWin, 0);
        MainCreate();
        // USER END
        break;
      // USER START (Optionally insert additional code for further notification handling)
      // USER END
      }

(二)控制操作图标切换

也就是实现Alarm、Fan、Led点击图片按钮可以实现开关图片的切换,以Alarm为例:
// USER START (Optionally insert additional static data)
extern GUI_CONST_STORAGE GUI_BITMAP bmSubAlarmClose;
extern GUI_CONST_STORAGE GUI_BITMAP bmSubAlarmOpen;
extern GUI_CONST_STORAGE GUI_BITMAP bmSubHome;
static int status = 0;
// USER END
    // USER START (Optionally insert additional code for further widget initialization)
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0);
    if(status){
        BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED, &bmSubAlarmOpen);
    }else{
        BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED, &bmSubAlarmClose);
    }
    hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_1);
    BUTTON_SetBitmap(hItem, BUTTON_BI_UNPRESSED,&bmSubHome);
    // USER END

PS:在按键0中添加代码

    case ID_BUTTON_0: // Notifications sent by ''
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        // USER START (Optionally insert code for reacting on notification message)
        // USER END
        break;
      case WM_NOTIFICATION_RELEASED:
        // USER START (Optionally insert code for reacting on notification message)
        status = !status;
        if(status){
            BUTTON_SetBitmap(pMsg->hWinSrc, BUTTON_BI_UNPRESSED, &bmSubAlarmOpen);

        }else{
            BUTTON_SetBitmap(pMsg->hWinSrc, BUTTON_BI_UNPRESSED, &bmSubAlarmClose);
        }
        // USER END
        break;
      // USER START (Optionally insert additional code for further notification handling)
      // USER END
      }
      break;

物联网智慧教室项目(六):人机交互功能开发(stm32)

一、中文字库生成原理

(一)点阵字体及字符编码

点阵字体

在这里插入图片描述

字符编码

ASC||

在这里插入图片描述

GB2312

在这里插入图片描述

Unicode

在这里插入图片描述

UTF-8

在这里插入图片描述

只要在点阵显示之前,进行调用一次就可以,一般都是在我们GUI_INIT 之后

在这里插入图片描述

emWin创建中文字库

FontCvtST

在这里插入图片描述

基于windows字库创建emWin字库

生成全部字库,需要占用35.2MB   STM32 flash也就是几百k

在这里插入图片描述

创建小型字库

在这里插入图片描述

(三)生成项目所需中文字库

温度
湿
光照
报警
灯光
风扇
传感器
控制

二、实现界面中文显示

Created with Raphaël 2.3.0 使能UTF-8编码方案 添加字库到工程中 设置c源文件(需要显示中文)为UTF-8编码 程序中引用字库 text小工具选择字库 打印字符内容

三、代码移植到STM32(上)

Created with Raphaël 2.3.0 文件拷贝 MDK工程添加 编译

(一)MDK工程编码格式修改为utf-8

在这里插入图片描述

(二)需要创建include_dlg.h

//在需要引用的地方进行头文件包含
#ifndef 	_DLG_H
#define 	_DLG_H
#include "DIALOG.h"

extern WM_HWIN TempCreate(void);

extern WM_HWIN MainCreate(void);

extern WM_HWIN LightCreate(void);

extern WM_HWIN LedCreate(void);

extern WM_HWIN HumCreate(void);

extern WM_HWIN FanCreate(void);

extern WM_HWIN AlarmCreate(void);
#endif

(三)在freeRTOS GUItask里调用 MainTask

/**
* @brief Function implementing the GuiTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Gui_Task */
void Gui_Task(void const * argument)
{
  /* USER CODE BEGIN Gui_Task */
  /* Infinite loop */
	MainTask();
  /* USER CODE END Gui_Task */
}

(四)在Touch任务里保留 Touch检测

/* USER CODE BEGIN Header_Touch_Task */
/**
  * @brief  Function implementing the TouchTask thread.
  * @param  argument: Not used 
  * @retval None
  */
/* USER CODE END Header_Touch_Task */
void Touch_Task(void const * argument)
{
    
                 
//  /* init code for FATFS */
//  MX_FATFS_Init();

//  /* init code for LWIP */
//  MX_LWIP_Init();
  /* USER CODE BEGIN Touch_Task */
	printf("system is runing!\r\n");
  /* Infinite loop */
  for(;;)
  {
		//ִ触摸屏需要轮询检测,不然emWin没有办法触发事件
		GUI_TOUCH_Exec();	
		osDelay(20);
  }
  /* USER CODE END Touch_Task */
}

四、代码移植到STM32(下)

Created with Raphaël 2.3.0 烧录测试 控制实际的硬件 采集实际的传感器

测试出的bug

bug1:当切换界面时,出现闪屏动作,体验感非常差
bug2:当频繁切换界面时,出现花屏或者白屏
在下一节进行解决

五、人机交互功能测试验证

(一)切换界面闪屏问题

使用存储设备

在这里插入图片描述

自动使能存储设备

在这里插入图片描述

使能存储设备API

在这里插入图片描述

频繁切换界面花屏问题

/*********************************************************************
*
*       GUI_X_Config
*
* Purpose:
*   Called during the initialization process in order to set up the
*   available memory for the GUI.
*/
void GUI_X_Config(void) {
  //
  // 32 bit aligned memory area
  //
	volatile U32* aMemory = (volatile U32*)(SRAM_BANK_ADDR);
  //
  // Assign memory to emWin
  GUI_ALLOC_AssignMemory((void *)aMemory, GUI_NUMBYTES);
  GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSUZE);
  //
  // Set default font
  //
  GUI_SetDefaultFont(GUI_FONT_32_1);
}

物联网智慧教室项目(七):网页交互功能开发(前端)

源码已通过百度云上传,推荐使用谷歌浏览器打开,使用开发者模式浏览学习,也可自行修改样式。
链接:https://pan.baidu.com/s/1gYXIK8YYXrwZbwsCJSS-MQ?pwd=gbls 
提取码:gbls

一、界面构思

(一)原型分析

在这里插入图片描述

(二)界面构思

1、标题栏

布局
标题设计
大标题:智慧教室管理系统
小标题:灯光,报警,风扇,温度,湿度,光照
样式设计
  • 背景色配置
  • 字体配置
    • 字体大小,格式,颜色

2、导航栏

布局
导航栏标题
提示用户用于智能管理使用的,用图片进行设计
导航标签
6个导航标签
3 个用于传感器
3 个用于控制
每一个标签,都有图标和文件进行展示
6个导航展示界面
3 个传感器
    显示传感器名称和传感器值
3 个控制
    显示控制名称和控制按钮
样式设计
当鼠标每点击一个导航标签,我都需要进行导航界面展示
前后台交互设计
  • 定时刷新传感器数据值(当用户点击传感器标签,需要把传感器值实时的展示给用户)
  • 控制下发(当用户点击控制标签,需要根据用户下发的命令进行控制实际的硬件,并且在主题栏进行动态展示)

3、主题栏

布局
  • 主题界面

    给用户展示一个智慧教室的图片
    
  • 风扇图标

    当用户开关风扇时,这个图标要进行动态效果
    
  • 报警器图标

    当用户开关报警器时,这个图标要进行动态效果
    
  • 灯光控制

    当用户开关灯泡时,我们教室的主界面要展示动态效果
    

(三)素材设计

1、导航栏素材

在这里插入图片描述

2、主题栏素材

在这里插入图片描述

在这里插入图片描述

二、前端开发技术

(一)开发工具

https://code.visualstudio.com/

在这里插入图片描述

(二)前端开发基本技能

html

在这里插入图片描述

CSS

在这里插入图片描述

JavaScript

在这里插入图片描述

jQuery

http://jquery.cuishifeng.cn/index.html

在这里插入图片描述

(三)基于框架开发

BootStrap

https://www.bootcss.com/

在这里插入图片描述

Vue

https://cn.vuejs.org/

在这里插入图片描述

react

https://react.docschina.org/

在这里插入图片描述

项目用到的技术

在这里插入图片描述

三、Web页面布局

(一)标题栏布局

在这里插入图片描述

    <div class="header">
        <div class="content">
            <h1>智慧教室管理系统
                <small>温度、湿度、风扇、报警,光照
                </small>
            </h1>
        </div>
    </div>

(二)导航栏布局

在这里插入图片描述

<div class="body-left">
    <img src="images/left-title.png" style="margin:26px 0;">
    <div>
        <ul class="left-nav">
            <li class="line active">
                <a href="#title1" data-toggle="tab">
                    <img src="images/tubiao01.png" width="40px">温度传感器
                </a>
            </li>
            <li class="line">
                <a href="#title2" data-toggle="tab">
                    <img src="images/tubiao02.png" width="40px">湿度传感器
                </a>
            </li>
            <li class="line">
                <a href="#title3" data-toggle="tab">
                    <img src="images/tubiao03.png" width="40px">光照传感器
                </a>
            </li>
            <li class="line">
                <a href="#title4" data-toggle="tab">
                    <img src="images/tubiao04.png" width="40px">LED
                </a>
            </li>
            <li class="line">
                <a href="#title5" data-toggle="tab">
                    <img src="images/tubiao05.png" width="38px">风扇
                </a>
            </li>
            <li class="line">
                <a href="#title6" data-toggle="tab">
                    <img src="images/tubiao06.png" width="40px">报警器
                </a>
            </li>
        </ul>
        <div class="content">
            <div class="box fade in active" id="title1">
                <p>温度值<br /><span>
                        <lable id="temperature"></lable>
                    </span></p>
            </div>
            <div class="box fade" id="title2">
                <p>湿度值<br /><span>
                        <lable id="humidity"></lable>
                    </span></p>
            </div>
            <div class="box fade" id="title3">
                <p>光照值<br /><span>
                        <lable id="light"></lable>
                    </span></p>
            </div>
            <div class="box fade" id="title4">
                <h3>开关</h3>
                <img id="button01" src="images/an-off.png" onclick="anniu01()" />
            </div>
            <div class="box fade" id="title5">
                <h3>开关</h3>
                <img id="button02" src="images/an-off.png" onclick="anniu02()" />
            </div>
            <div class="box fade" id="title6">
                <h3>开关</h3>
                <img id="button03" src="images/an-off.png" onclick="anniu03()" />
            </div>
        </div>
    </div>

(三)背景栏布局

在这里插入图片描述

<div class="body-right">
    <img id="sgbj" class="sgbj" src="images/sgbj-off.png" />
    <img id="fan" class="fan" src="images/fan-off.png" />
    <img id="bg" src="images/sys-bg.jpg" />
    <audio id="alarm" src="music/alarm.mp3"></audio>
</div>

四、Web页面样式设计

(一)外部文件引入

<link rel="stylesheet" type="text/css" href="css/bootstrap.css" />
<script src="js/jquery-1.11.0.min.js" type="text/javascript"></script>
<script src="js/bootstrap.min.js" type="text/javascript"></script>

(二)标题栏样式设计

        h1 {
            padding: 0; 
            margin: 0 0 0 15px;
            font-size: 24px;
            color: #fff;
            line-height: 3em;
            font-weight: 100;
        }

        h1 small {
            font-size: 12px;
            color: #fff;
            margin-left: 10px;
        }

        .content {
            width: 1200px;
            margin: 0 auto;
        }

(三)导航栏样式设计

.body-left {
    float: left;
    width: 311px;
    margin-right: 10px;
    text-align: center;
    color: #fff;
}

.body-left .left-nav {
    float: left;
    width: 180px;
    list-style: none;
    margin: 0;
    padding: 0;
    text-align: left;
}
.body-left .left-nav .line {
    background: #010146;
}

.body-left .left-nav .active {
    background: #020220;
}

.body-left .left-nav a {
    position: relative;
    display: block;
    line-height: 63px;
    text-decoration: none;
    color: #fff;
    padding-left: 60px;
}

.body-left .left-nav a img {
    position: absolute;
    top: 7px;
    left: 10px;
}

.body-left .content {
    float: left;
    width: 131px;
    background: #020220;
    height: 378px;
}

.body-left .content .box {
    position: absolute;
    margin: 20px 10px;
    width: 111px;
}

.body-left .content .box h3 {
    font-size: 16px;
    padding: 0;
    margin: 20px 0;
}

.body-left .content .box p {
    padding: 0;
    margin: 10px 0;
}

.body-left .content .box p span {
    font-size: 20px;
    color: #FFFF00;
}

.body-left .content .active {
    z-index: 10;
}

#button {
    width: 80px;
}

(四)主题栏样式设计

.body-right {
    position: relative;
    float: left;
    width: 879px;
}

.body-right .sgbj {
    position: absolute;
    top: 135px;
    left: 12px;
    width: 50px;
}

.body-right .fan {
    position: absolute;
    top: 186px;
    left: 473px;
    width: 40px;
}

(五)样式效果设计

1、bootstrap教程

https://www.runoob.com/bootstrap/bootstrap-tab-plugin.html

2、导航标签样式设计

<li class="line active">
    <a href="#title1" data-toggle="tab">
        <img src="images/tubiao01.png" width="40px">温度传感器
    </a>
</li>

3、导航标签内容设计

<div class="box fade in active" id="title1">
    <p>温度值<br /><span>
            <lable id="temperature"></lable>
        </span></p>
</div>

五、前后台交互设计

(一)传感器数据获取

1、传感器数据初始化

$("#temperature").text("25℃");
$("#humidity").text("45%");
$("#light").text("1233lux");

2、ajax后台数据获取

//获取传感器数据
function ReadData() {
    $.ajax({
        url: "/DATA/Sensor",
        type: "get",
        cache: false,
        timeout: 2000,
        dataType: "json",
        success: function (data) {
            console.log(data);
            $("#temperature").text(data.temperature + "℃");
            $("#humidity").text(data.humidity + "%");
            $("#light").text(data.light + "lux");
        }
    })
}
        //命令交互
        function SendCmd(type, cmd) {
            $.ajax({
                url: "/CMD/" + type,
                type: "post",
                cache: false,
                timeout: 2000,
                data: cmd,
                success: function (data) {
                    console.log(data);
                }
            })


        }

(二)定时刷新传感器数据

setInterval(ReadData, 5000);

(三)按钮命令交互

1、灯光控制

function anniu01() {
    var anNiu = $("#button01")[0];
    var bG = $("#bg")[0];
    if (LightStatus) {
        //发送关闭灯光命令
        SendCmd("Light", "Off");
        anNiu.src = ("images/an-off.png");
        bG.src = ("images/sys-bg-off.jpg");
        bG = $("#fan")[0];
        if (FanStatus) {
            bG.src = ("images/bg-fan-on.png");
        }
        else {
            bG.src = ("images/bg-fan-off.png");
        }
        LightStatus = false;
    } else {
        //发送开启灯光命令
        SendCmd("Light", "On");
        anNiu.src = ("images/an-on.png");
        bG.src = ("images/sys-bg.jpg");
        bG = $("#fan")[0];
        if (FanStatus) {
            bG.src = ("images/fan-on.png");
        }
        else {
            bG.src = ("images/fan-off.png");
        }
        LightStatus = true;

    }
}

2、风扇控制

function anniu02() {
    var anNiu = $("#button02")[0];
    var bG = $("#fan")[0];
    if (FanStatus) {
        //发送关闭风扇命令
        SendCmd("Fan", "Off");
        anNiu.src = ("images/an-off.png");
        if (LightStatus) {
            bG.src = ("images/fan-off.png");
        }
        else {
            bG.src = ("images/bg-fan-off.png");
        }

        FanStatus = false;
    }
    else {
        //发送开启风扇命令
        SendCmd("Fan", "On");
        anNiu.src = ("images/an-on.png");
        if (LightStatus) {
            bG.src = ("images/fan-on.png");
        }
        else {
            bG.src = ("images/bg-fan-on.png");
        }

        FanStatus = true;
    }
}

3、报警控制

function anniu03() {
    var anNiu = $("#button03")[0];
    var bG = $("#sgbj")[0];
    var AS = $("#alarm")[0];
    if (AlarmStatus) {
        //发送关闭风扇命令
        SendCmd("Alarm", "Off");
        anNiu.src = ("images/an-off.png");
        bG.src = ("images/sgbj-off.png");
        AS.removeEventListener('ended', loop, false);
        AS.pause();
        AlarmStatus = false;
    }
    else {
        //发送开启风扇命令
        SendCmd("Alarm", "On");
        anNiu.src = ("images/an-on.png");
        bG.src = ("images/sgbj-on.gif");
        AS.addEventListener('ended', loop, false);
        AS.play();
        AlarmStatus = true;
    }
}

物联网智慧教室项目(八):网页web服务器功能开发(stm32)

一、WebServer功能设计

(一)WebServer需要做什么

当用户访问网址(url)时,加载网页文件

在这里插入图片描述

当用户点击控制按钮,传感器定时刷新与服务器进行交互

在这里插入图片描述

(二)EasyWebSvr工具介绍

菜单界面

在这里插入图片描述

设置界面

在这里插入图片描述

运行日志

在这里插入图片描述

(三)EasyWebSvr搭建Web服务器

配置服务器主目录

在这里插入图片描述

浏览器输入服务器地址

127.0.0.1/index.html

在这里插入图片描述

分析Web服务器运行日志

在这里插入图片描述

(四)WebServer功能设计

文件请求响应

Browser WebServer FileSystem GET /index.html open(index.html) index.html Response index.html Browser WebServer FileSystem

传感器数据请求响应

Browser WebServer SensorTask GET /DATA/Sensor Read SensorData SensorData Response SensorData Browser WebServer SensorTask

命令请求响应

Browser WebServer CMDTask POST /CMD/Light Send Cmd Cmd Status Response Cmd Status Browser WebServer CMDTask

二、WebServer主线程实现

(一)WebServer代码移植

文件移植

智慧教室项目实战\day08\03-WebServer移植文件

在这里插入图片描述

把网页文件拷贝到SD卡中

1.拷贝文件到SD卡"根目录下"
2.开发板断电插入SD卡
3.烧录程序看现象(浏览器输入192.168.1.7

在这里插入图片描述

访问STM32服务器,网页图片加载需要多次刷新问题

1、我们webserver是一个单线程任务,http属于短链接//http没有记忆功能,在一次socket通信中能获取多少数据,就只能获取多少数据
2、但是浏览器有缓存,我们使用时只需要多刷新几次就可以了
3、也可以通过在前端增加一些js代码(循环加载前端资源(文件))
4、STM32内存太小了,没有办法做长连接,短连接模式可以实现多个客户端连接

(二)WebServer主线程实现

http_server_socket_thread

/**
  * @brief  http server thread 
  * @param arg: pointer on argument(not used here) 
  * @retval None
  */
void http_server_socket_thread(void *p_arg)
{
	int sock, newconn, size;
	struct sockaddr_in address, remotehost;

	/* create a TCP socket */
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		printf("http_server can not create socket");
		return;
	}

	/* bind to port 80 at any interface */
	address.sin_family = AF_INET;
	address.sin_port = htons(80);
	address.sin_addr.s_addr = INADDR_ANY;

	if (bind(sock, (struct sockaddr *)&address, sizeof(address)) == -1)
	{
		printf("http_server can not bind socket");
		return;
	}

	/* listen for incoming connections (TCP listen backlog = 5) */
	listen(sock, 5);

	size = sizeof(remotehost);
/*先不关心,但是很重要
	printf("\r\n--------------Web_Server_Task----------------\r\n");
	WEB_Service_Registration(&CONTROL_LIGHT_CMD_POST);
	WEB_Service_Registration(&CONTROL_FAN_CMD_POST);
	WEB_Service_Registration(&CONTROL_ALARM_CMD_POST);
*/
// 循环等待客户端的接入
	while (1)
	{
        //等待客户端接入
		newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size);
        //针对客户端做服务处理  请求-->响应
		http_server_serve(newconn);
	}
}

http_server_serve

/**
  * @brief serve tcp connection  
  * @param conn: connection socket 
  * @retval None
  */
void http_server_serve(int conn)
{
	int ret;
	/* Read in the request */
	ret = read(conn, (unsigned char *)Request_Buf, 1500);

	if (ret < 0)
		return;
	else
	{
         //把请求内容最后一个字节填充\0,以后直接用字符串解析
		*(Request_Buf + ret) = 0;
#ifdef WEB_DEBUG
		printf("\r\nWEB服务器,接收请求,内容:\r\n%s\r\n", (const char *)Request_Buf);
#endif
        //请求响应代码
		Respond_Http_Request(conn, (char *)Request_Buf);
	}
	//关闭socket 这就一个短链接的实现
	close(conn);
}

(三)http解析业务流程

请求解析
方法类型
ERROR
GET
POST
错误响应
GET类型
数据读取响应
文件读取响应
POST类型
命令下发响应
文件写入响应

三、Http文件请求响应

(一)数据结构

http文件请求响应首部封装

#define HOMEPAGE_DEFAULT "index.html"

#define INVALID_CMD "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nContent-Length: 16\r\n\r\ninvalid cmd!"
#define POST_REQUEST_OK "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/css\r\nCache-control: no-cache\r\nExpires: Thu, 15 Apr 2000 20:00:00 GMT\r\nContent-Length: 18\r\n\r\nPOST Successfully!"
#define POST_REQUEST_FAIL "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/css\r\nCache-control: no-cache\r\nExpires: Thu, 15 Apr 2000 20:00:00 GMT\r\nContent-Length: 13\r\n\r\nPOST Failure!"
#define RETURN_cmd_OK "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nCache-Control: no-cache, no-store, max-age=0\r\nExpires: 1L\r\nConnection: close\r\nContent-Length: "
/* html文件请求错误响应 */
const char ERROR_HTML_PAGE[] = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 78\r\n\r\n<HTML>\r\n<BODY>\r\nSorry, the page you requested was not found.\r\n</BODY>\r\n</HTML>\r\n\0";
/* 数据命令请求错误响应 */
const char ERROR_REQUEST_PAGE[] = "HTTP/1.0 500 Pafe Not Found\r\nConnection: close\r\nContent-Type: text/html\r\nContent-Length: 50\r\n\r\n<HTML>\r\n<BODY>\r\nInvalid request.\r\n</BODY>\r\n</HTML>\r\n\0";
/* Response header for HTML*/
const char RES_HTMLHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/html\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for TEXT */
const char RES_TEXTHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/plain\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for GIF */
const char RES_GIFHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/gif\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for JPEG */
const char RES_JPEGHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/jpeg\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for MPEG */
const char RES_PNGHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/png\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for MP3 */
const char RES_MP3HEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: audio/mpeg\r\nContent-Range: bytes 0-40123/40124\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for JS */
const char RES_JSHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\ncontent-type:application/x-javascript\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for ICO */
const char RES_ICOHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/x-icon\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for CSS */
const char RES_CSSHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/css\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
/* Response head for APP */
const char RES_APP_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: application/octet-stream\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";

http消息结构体

typedef struct
{
	char Method;				 //请求方法: GET 、HEAD 、POST
	char *URL;					 //URL
	char FileType;				 //文件类型
	char *Post_Data;			 //POST数据
	unsigned int Content_Length; //POST数据的长度
} Http_Request_MSG_Type;		 //定义HTTP请求报文消息结构体

(二)Respond_Http_Request

/*******************************************************************************
* 函数名称:  void Respond_Http_Request(SOCKET ch ,char* Request_Msg)
* 函数说明: 响应HTTP请求
* 输入参数: socket 端口:ch ;HTTP请求数据包指针	:Request_Msg
* 返回参数: 无
*******************************************************************************/
void Respond_Http_Request(char ch, char *Request_Msg)
{
    //创建http解析结构体
	Http_Request_MSG_Type Http_Request_MSG;
    //定义两个指针,用于解析数据记录跟踪
	char *thisstart = NULL;
	char *nextstart = NULL;
    //缓冲数据指针
	char *buf;
    //缓存数据长度
	int length = 0;
    //进行数据解析,解析完毕后,会填充Http_Request_MSG
	Parse_Http_Request(Request_Msg, &Http_Request_MSG); //解析HTTP请求类型
	switch (Http_Request_MSG.Method)
	{
	case METHOD_ERR:
         //把错误响应返回
		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
		break;
	case METHOD_HEAD:
	case METHOD_GET:
		if (strstr((const char *)Http_Request_MSG.URL, "/DATA/")) //判断是否是通讯指令,并解析指令帧
		{
			//传感器数据请求响应
		}
		else
		{
            //响应文件请求
			Send_Response_File(ch, &Http_Request_MSG); //发送请求的文件
		}
		break;
	case METHOD_POST:
		//post响应
	}	
}

(三)Parse_Http_Request

/*******************************************************************************
* 函数名称:  void Parse_Http_Request(char * Request_Msg ,Http_Request_MSG_Type *Http_Request_MSG)
* 函数说明: 解析HTTP请求
* 输入参数: 请求数据包指针:Request_Msg ;  请求信息类型 :Http_Request_MSG
* 返回参数: 无
*******************************************************************************/
void Parse_Http_Request(char *Request_Msg, Http_Request_MSG_Type *Http_Request_MSG)
{
	char *thisstart = NULL;
	char *nextstart = NULL;

	thisstart = strtok_r(Request_Msg, " ", &nextstart);
	if (thisstart == NULL)
	{
		Http_Request_MSG->Method = METHOD_ERR;
		Http_Request_MSG->URL = NULL;
		return;
	}
	//解析请求所用的方法:GET,HEAD,POST
	if (!strcmp(thisstart, "GET") || !strcmp(thisstart, "get"))
	{
		Http_Request_MSG->Method = METHOD_GET;
	}
	else if (!strcmp(thisstart, "HEAD") || !strcmp(thisstart, "head"))
	{
		Http_Request_MSG->Method = METHOD_HEAD;
	}
	else if (!strcmp(thisstart, "POST") || !strcmp(thisstart, "post"))
	{
		Http_Request_MSG->Method = METHOD_POST;
	}
	else
	{
		Http_Request_MSG->Method = METHOD_ERR;
		Http_Request_MSG->URL = NULL;
		return;
	}

	if (nextstart == NULL)
	{
		Http_Request_MSG->Method = METHOD_ERR;
		Http_Request_MSG->URL = NULL;
		return;
	}

	Http_Request_MSG->URL = strtok_r(NULL, " ?", &nextstart);				 //解析URL
	if (Http_Request_MSG->URL[0] == '/' && Http_Request_MSG->URL[1] == '\0') //如果url仅是一个“/”,则默认为主页
	{
		Http_Request_MSG->URL = HOMEPAGE_DEFAULT; //设置默认页
	}
	Http_Request_MSG->Post_Data = nextstart; //保存下一字符串指针
}

(四)Send_Response_File

/*******************************************************************************
* 函数名称:  void Send_Response_File(SOCKET ch,Http_Request_MSG_Type *Http_Request_MSG) 
* 函数说明: 回应请求的文件
* 输入参数: SOCKET 通道号  ,Http_Request_MSG_Type  文件类型信息
* 返回参数: 无
*******************************************************************************/
void Send_Response_File(char ch, Http_Request_MSG_Type *Http_Request_MSG)
{
	FRESULT res;
	FIL *f;
	unsigned int bytes_ret;
	unsigned char *buf;
	unsigned char *buf1;
	uint32_t fSize;
	
	f = (FIL *)pvPortMalloc(sizeof(FIL));		//开辟内存空间
	buf = (unsigned char *)pvPortMalloc(1500);	//开辟内存空间
	buf1 = (unsigned char *)pvPortMalloc(1500); //开辟内存空间
	if (f == NULL || buf == NULL || buf1 == NULL)
	{
		printf("内存分配失败\r\n");
		vPortFree(f);			 //释放内存空间
		vPortFree(buf);			 //释放内存空间
		vPortFree(buf1);		 //释放内存空间
		
		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
		return;
	}
    //打开文件
	res = f_open(f, Http_Request_MSG->URL, FA_OPEN_EXISTING | FA_READ);
    //获取文件大小
	fSize = f_size(f);
	printf("http file size is %d\r\n", fSize);
	if (res == FR_OK)
	{
		Http_Request_MSG->FileType = Parse_URL_File_Type(Http_Request_MSG->URL); //分析请求URL中包含的文件的文件类型
		Make_http_response_head((char *)buf, Http_Request_MSG->FileType, fSize); //生成HTTP报文
        //响应首部
		write(ch, (const unsigned char *)buf, strlen((const char *)buf));
        //响应主题------- 文件
		while (1)
		{
            //读取文件
			res = f_read(f, buf, 1500, &bytes_ret);
            //读取文件错误
			if (res != FR_OK)
			{
				printf("读取文件失败!文件名:%s,错误代码:0x%02x 文件大小:%d\r\n", (const char *)Http_Request_MSG->URL, res, bytes_ret);
				SD_initialize(0);
				break;
			}
            //读取文件内容为空
			if (bytes_ret == 0)
				break;
            //读取文件正确,写回socket
			write(ch, (const unsigned char *)buf, bytes_ret);
            //继续读取文件
			res = f_read(f, buf1, 1500, &bytes_ret);
			if (res != FR_OK)
			{
				printf("读取文件失败!文件名:%s,错误代码:0x%02x 文件大小:%d\r\n", (const char *)Http_Request_MSG->URL, res, bytes_ret);
				SD_initialize(0);
				break;
			}
			if (bytes_ret == 0)
				break;
            //读取文件正确写回
			write(ch, (const unsigned char *)buf1, bytes_ret);
		}
        //关闭文件
		f_close(f);
		vPortFree(f);			 //释放内存空间
		vPortFree(buf);			 //释放内存空间
		vPortFree(buf1);		 //释放内存空间
		
	}
	else //文件打开错误
	{
		f_close(f);
		vPortFree(f);			 //释放内存空间
		vPortFree(buf);			 //释放内存空间
		vPortFree(buf1);		 //释放内存空间
		//写入错误请求响应
		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
		printf("打开文件失败!文件名:%s,错误代码:0x%02x\r\n", (const char *)Http_Request_MSG->URL, res);
		SD_initialize(0);
	}
}

四、前后台交互方法设计

(一)数据结构

typedef struct Web_s{
	struct  Web_s 	*next;   //单链表节点 
	const char      *cmd;    //get&post具体消息内容存放位置
	void            (*function)(void *,void *);//不同响应的处理方法
} WEB_Server_Struct;

(二)交互数据结构封装

字符串封装

const char Sensor[] = "Sensor";	// get方法内消息 /DATA/Sensor 用来解析此请求
const char Light[] = "Light";//post方法内消息 /CMD/Light 用来解析此请求
const char Fan[] = "Fan";//post方法内消息 /CMD/Fan 用来解析此请求
const char Alarm[] = "Alarm";//post方法内消息 /CMD/Alarm 用来解析此请求
const char On[] = "On";//post方法内消息 /CMD/xxx 消息内容为开启
const char Off[] = "Off";//post方法内消息 /CMD/xxx 消息内容为关闭

数据节点封装

//我们项目中需要4中交互,传感器数据获取,开关灯,风扇,报警器,定义下面结构体
WEB_Server_Struct SENSOR_WEB_DATA_GET = {NULL, Sensor, Get_SensorValue};
WEB_Server_Struct CONTROL_LIGHT_CMD_POST = {NULL, Light, Post_Cmd_Light};
WEB_Server_Struct CONTROL_FAN_CMD_POST = {NULL, Fan, Post_Cmd_Fan};
WEB_Server_Struct CONTROL_ALARM_CMD_POST = {NULL, Alarm, Post_Cmd_Alarm};

交互函数封装

/****************************************************************************************************
 函数原型:void Get_SensorValue(void *buffer,void *value)
 入口参数:发送缓冲区指针,设定值指针
 出口参数:无
 函数功能:登录验证
 ****************************************************************************************************/
static void Get_SensorValue(void *buffer, void *value)
{
	//把传感器数据填充到我们的buffer里面,之后进行响应就ok了
	sprintf(buffer, "{\"temperature\":\"%d\",\"humidity\":\"%d\",\"light\":\"10021.1\"}", SensorData[0], SensorData[1]);
}

/****************************************************************************************************
 函数原型:void Post_Cmd_Light(void *buffer,void *value)
 入口参数:发送缓冲区指针,设定值指针
 出口参数:无
 函数功能:登录验证
 ****************************************************************************************************/
static void Post_Cmd_Light(void *buffer, void *value)
{
	//判断value是on还是off
	if (strstr(value, On))
	{   
         //响应状态为on  Status:On
		sprintf(buffer, "{\"Status\":\"On\"}");
		HAL_GPIO_WritePin(GPIOF, D6_Pin | D7_Pin | D8_Pin | D9_Pin, GPIO_PIN_RESET);
	}
	else if (strstr(value, Off))
	{	
          //响应状态为Off Status:Off
		sprintf(buffer, "{\"Status\":\"Off\"}");
		HAL_GPIO_WritePin(GPIOF, D6_Pin | D7_Pin | D8_Pin | D9_Pin, GPIO_PIN_SET);
	}
	else
	{
         //响应错误
		sprintf(buffer, "{\"Status\":\"Error\"}");
	}
}

/****************************************************************************************************
 函数原型:void Post_Cmd_Fan(void *buffer,void *value)
 入口参数:发送缓冲区指针,设定值指针
 出口参数:无
 函数功能:登录验证
 ****************************************************************************************************/
static void Post_Cmd_Fan(void *buffer, void *value)
{
	if (strstr(value, On))
	{
		sprintf(buffer, "{\"Status\":\"On\"}");
        //风扇控制  ----- 后面zigbee项目讲解
		FanControl(0x01);
	}
	else if (strstr(value, Off))
	{
		sprintf(buffer, "{\"Status\":\"Off\"}");
		FanControl(0x0);
	}
	else
	{
		sprintf(buffer, "{\"Status\":\"Error\"}");
	}
}

/****************************************************************************************************
 函数原型:void Post_Cmd_Alarm(void *buffer,void *value)
 入口参数:发送缓冲区指针,设定值指针
 出口参数:无
 函数功能:登录验证
 ****************************************************************************************************/
static void Post_Cmd_Alarm(void *buffer, void *value)
{
	if (strstr(value, On))
	{
		sprintf(buffer, "{\"Status\":\"On\"}");
		HAL_GPIO_WritePin(BUZ_GPIO_Port, BUZ_Pin, GPIO_PIN_SET);
	}
	else if (strstr(value, Off))
	{
		sprintf(buffer, "{\"Status\":\"Off\"}");
		HAL_GPIO_WritePin(BUZ_GPIO_Port, BUZ_Pin, GPIO_PIN_RESET);
	}
	else
	{
		sprintf(buffer, "{\"Status\":\"Error\"}");
	}
}

(三)交互数据结构处理

/*******************************************************************************
* 函数名称:  void WEB_Service_Registration(WEB_Server_Struct *next)
* 函数说明: WEB数据服务注册,与应用程序间的映射建立。
* 输入参数: 相应应用程序的链表类型指针
* 返回参数: 无
*******************************************************************************/
void WEB_Service_Registration(WEB_Server_Struct *next)
{
    //首先获取头结点
	WEB_Server_Struct *f = WEB_Registry_Head;
    //传入的节点下一个指向空
	next->next = NULL;
    //遍历找到空节点位置
	while (f->next != NULL)
	{
		f = f->next;
	}
    //把节点插入到链表中
	f->next = next;
}

/*******************************************************************************
* 函数名称:  char Search_match_the_analytical_method(const char *cmd , char *body_Buf)
* 函数说明: 根据命令搜寻匹配解析方法
* 输入参数: WBE网页发来的命令
* 返回参数: 搜寻匹配成功返回0  ; 未找到匹配命令返回1
*******************************************************************************/
char Search_match_the_analytical_method(const char *cmd, char *body_Buf)
{
    //找到头结点
	WEB_Server_Struct *f = WEB_Registry_Head;
	char *p;

	printf("CMD:\r\n");
	printf("%s\r\n", cmd);
	for (f = WEB_Registry_Head; f != NULL; f = f->next)
	{
        //判断cmd是否在链表内
		p = strstr(cmd, f->cmd);
		if (p != NULL)
		{
             //获取命令数据
			p = (char *)cmd;
            //获取命令 value指针
			p = p + strlen(f->cmd) + 1;
            //进行响应处理
			f->function(body_Buf, p); //此函数不可重入,所以停止任务调度
			return 1;
		}
	}
	return 0;
}
/*******************************************************************************
* 函数名称:  char POST_Search_match_the_analytical_method(char *cmd,char *dat)
* 函数说明: POST方式下获取的命令和数据,根据命令搜寻匹配解析方法
* 输入参数: WBE网页POST发来的命令和数据包
* 返回参数: 搜寻匹配成功返回0  ; 未找到匹配命令返回1
*******************************************************************************/
char POST_Search_match_the_analytical_method(char *cmd, char *body_buf, char *dat)
{
	WEB_Server_Struct *f = WEB_Registry_Head;

	for (f = WEB_Registry_Head; f != NULL; f = f->next)
	{
		if (strstr(cmd, f->cmd))
		{
            //dat在上层应用获取到,直接传入value值
			f->function((char *)body_buf, dat); //此函数不可重入,所以停止任务调度
			return 1;
		}
	}
	return 0;
}

五、前后台交互实现

(一)初始化

//把我们传感器数据获取节点,定义为链表的头结点
#define WEB_Registry_Head 	 &SENSOR_WEB_DATA_GET
/**
  * @brief  http server thread 
  * @param arg: pointer on argument(not used here) 
  * @retval None
  */
void http_server_socket_thread(void *p_arg)
{
    //.......

	printf("\r\n--------------Web_Server_Task----------------\r\n");
    //把CMD相关的节点插入到链表当中
	WEB_Service_Registration(&CONTROL_LIGHT_CMD_POST);
	WEB_Service_Registration(&CONTROL_FAN_CMD_POST);
	WEB_Service_Registration(&CONTROL_ALARM_CMD_POST);
    
    //........

}

(二)前台交互解析

/*******************************************************************************
* 函数名称:  void Respond_Http_Request(SOCKET ch ,char* Request_Msg)
* 函数说明: 响应HTTP请求
* 输入参数: socket 端口:ch ;HTTP请求数据包指针	:Request_Msg
* 返回参数: 无
*******************************************************************************/
void Respond_Http_Request(char ch, char *Request_Msg)
{
	Http_Request_MSG_Type Http_Request_MSG;
	char *thisstart = NULL;
	char *nextstart = NULL;
	char *buf;
	int length = 0;
	Parse_Http_Request(Request_Msg, &Http_Request_MSG); //解析HTTP请求类型
	switch (Http_Request_MSG.Method)
	{
	case METHOD_ERR:
		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
		break;
	case METHOD_HEAD:
	case METHOD_GET:
		if (strstr((const char *)Http_Request_MSG.URL, "/DATA/")) //判断是否是通讯指令,并解析指令帧
		{
			char *buf;
			buf = (char *)pvPortMalloc(128);									
			if (Search_match_the_analytical_method((const char *)(Http_Request_MSG.URL + 6), buf)) //匹配解析方法,匹配成功,执行相应动作
			{
				Send_Web_Service_Data(ch, buf); //回应请求的数据
			}
			else
			{
				write(ch, INVALID_CMD, sizeof(INVALID_CMD)); //无此指令,回应无效请求
			}
			vPortFree(buf); //释放内存空间
		}
		else
		{
            //发送请求的文件
		}
		break;
	case METHOD_POST:
		//获取POST内容
		thisstart = strstr(Http_Request_MSG.Post_Data, "Content-Length:");
		if (thisstart != NULL)
		{
			Http_Request_MSG.Content_Length = atoi(thisstart + 15); //获取POST内容的大小
		}
		thisstart = strstr(thisstart, "\r\n\r\n") + 4;
         //Post_Data = "On"/"Off"
		Http_Request_MSG.Post_Data = thisstart;
		length = strlen(thisstart); //修改bug

		//解析POST内存
		if (strstr((const char *)Http_Request_MSG.URL, "/CMD/")) //判断是否是通讯指令,并解析指令帧
		{
			//本次接收的数据尾指针获取
			nextstart = thisstart + length;
             //nextstart - thisstart 本次socket接收数据的内容
            // Content_Length 是这次post body数据的长度
			while ((nextstart - thisstart) < Http_Request_MSG.Content_Length) //可能数据量很大,获取POST完整内容
			{
                 //后续还有数据,再调用read进行读取
				length = read(ch, (unsigned char *)nextstart, 1500);
				if (length > 0)
                      //把数据尾指针更新
					nextstart += length;
                      //没有后续数据
				else if (length < 0)
					break;
			}
             //填充\0保证一个完整字符串
			*nextstart = '\0';
             //更新整个post下发的长度字段
			Http_Request_MSG.Content_Length = nextstart - thisstart;

			//解析数据包
			buf = (char *)pvPortMalloc(128); //开辟内存空间--原先2048
			if (POST_Search_match_the_analytical_method(Http_Request_MSG.URL + 5, buf, Http_Request_MSG.Post_Data))
			{
				Send_Web_Service_Data(ch, buf);
			}
			else
			{
				write(ch, INVALID_CMD, sizeof(INVALID_CMD));
			}
			vPortFree(buf);
		}
		else
		{
            	//下发文件
		}
		break;
	default:
		break;
	}
}

(三)前后台交互响应

/*******************************************************************************
* 函数名称:  void Send_Web_Service_Data(SOCKET ch,char*body_buf) 
* 函数说明: 发送Web回应数据包 
* 输入参数: SOCKET 通道号 ,body_buf 要发送的数据包
* 返回参数: 无
*******************************************************************************/
void Send_Web_Service_Data(char ch, char *body_buf)
{
	char *buf;
	char *P_index;
	short length = 0;
    //获取body长度
	length = strlen((const char *)body_buf);
    //开辟内存空间
	buf = (char *)pvPortMalloc(length + sizeof(RETURN_cmd_OK) + 50); //开辟内存空间
	sprintf(buf, "%s%u\r\n\r\n%s", RETURN_cmd_OK, length, body_buf);
	length = strlen((const char *)buf);
	P_index = buf;
    //写入socket按照1500个字节进行传输
	while (length > 1500)
	{
         //如果write单次发送的长度太大,lwip占用动态内存比较多,这是时候,我们需要手动分片
		write(ch, (const unsigned char *)P_index, 1500);
		length -= 1500;
		P_index += 1500;
	}
	if (length > 0)
	{
		write(ch, (const unsigned char *)P_index, length);
	}
	vPortFree(buf); //释放内存空间
}

物联网智慧教室项目(九):ZigBee采集控制功能开发

一、zigbee采集控制功能实现

(一)zigbee采集控制功能硬件连接

在这里插入图片描述

(二)zigbee组网代码介绍及烧写

1、硬件连接

协调器连接

在这里插入图片描述

温湿度&风扇连接

在这里插入图片描述

2、代码路径

文件路径
智慧教室项目实战\day09\03-Zigbee源码
IAR工程路径
智慧教室项目实战\day09\03-Zigbee源码\Projects\zstack\Samples\ZigbeeApp\CC2530DB

3、代码编译配置

协调器编译

在这里插入图片描述

温湿度节点编译

在这里插入图片描述

风扇节点编译

在这里插入图片描述

三、STM32实现代码移植及分析

文件路径

智慧教室项目实战\02-智慧教室项目实战\day09\02-STM32需要移植的代码

zigbee应用协议解析实现

protocol.c protocol.h

zigbee串口数据接收

usart.c
    //主要配置使能uart1空闲中断
    
freertos.c
	//创建zigbee消息队列
    //在zigbee任务中进行接收消息,解析消息
stm32f4xx_it.c
    //zigbee消息入队

数据封装

CmdAndSensor.c
    //增加了风扇控制

webserver

httpserver-socket.c
    //传感器数据获取
    //风扇控制

触摸屏展示

TempDLG.c
HumDLG.c
LightDLG.c
    //增加传感器数据动态展示功能
FanDLG.c
    //增加实际的风扇控制功能

整体功能验证

二、Zigbee组网设计

(一)组网架构

使用Zigbee技术组建无线网络,网络中包含温湿度采集节点、风扇控制节点、Zigbee协调器

  • Zigbee协调器负责Zigbee网络组建,通过串口与STM32进行通信
  • STM32负责Zigbee传感器数据解析,和控制命令下发

在这里插入图片描述

(二)通信协议

协议格式

  1. 包头/版本:表示一包完整的数据/当前使用的协议版本

  2. 节点ID:在Zigbee网络里的节点ID号

  3. 节点类型:

    1. 0x0:温湿度传感器
    2. 0x01:风扇控制
  4. 数据长度:

    1. 范围:0~255
    2. 单位:字节
  5. 数据域:

  6. 温湿度传感器:温度+湿度(1+1字节)

  7. 风扇:风扇状态 (1个字节)

  8. CRC-8

  9. 8位循环冗余校验(保证通信可靠)

  10. 校验域为节点ID~数据域

包头节点ID节点类型数据长度数据域CRC-8包尾/版本
0x990x0~0XxFFFF0x0~0xFF0x0~0xFFxxCRC0x01
温湿度协议格式

上行

包头节点ID节点类型数据长度数据域CRC-8包尾/版本
0x990x00010x002温度:25℃,湿度:80%CRC0x01
风扇协议格式

上行

包头节点ID节点类型数据长度数据域CRC-8包尾/版本
0x990x00020x011开:0x01,关:0x00CRC0x01

下行

包头节点ID节点类型数据长度数据域CRC-8包尾/版本
0x990x00020x011开:0x01,关:0x00CRC0x01

三、Zigbee组网设计

通信方式

Zigbee支持单播,组播,广播,绑定通信,根据我们功能定义,协调器可以采用广播通信,节点采用单播通信
协调器地址恒为:0x0000
广播地址为:0xFFFF
节点地址:采用短地址通信,但是地址是有协调器进行动态分配的

协议解析 组包设计

在实际的网络的通信中即有上行又有上行,需要我们通过代码具体实现

####设计思路

  1. 判断接收协议是否合法
    1. 检查协议头
    2. 检查协议尾
    3. 校验CRC
  2. 获取有效数据
  3. 解析数据
  4. 创建协议数据

四、协议组包拆包功能实现

CRC教研算法

在这里插入图片描述

/***************************************************************************
* File                  : protocol.c
* Function Name         : 
* Description           : 协议处理
* Version               : v1.0
* Author                : zhengdao.liu
* Date                  : 2019/8/22
* Parameter             :
* Return                :
* Note                  :
***************************************************************************/

#include "protocol.h"

#include "stdio.h"

#include "string.h"

extern UART_HandleTypeDef huart1;

ProtocolDataType  ProtocolData = {
    HEAD,
    NODE_ID,
    NODE_TYPE,
    DATA_LEN,
    0x0,
    0x0,
    TAIL,
};


/******************************************************************************
 * Name:    CRC-8               x8+x2+x+1
 * Poly:    0x07
 * Init:    0x00
 * Refin:   False
 * Refout:  False
 * Xorout:  0x00
 * Note:
 *****************************************************************************/
uint8 crc8(uint8 *data, uint8 length)
{
    uint8 i;
    uint8 crc = 0;        // Initial value
    while(length--)
    {
        crc ^= *data++;        // crc ^= *data; data++;
        for ( i = 0; i < 8; i++ )
        {
            if ( crc & 0x80 )
                crc = (crc << 1) ^ 0x07;
            else
                crc <<= 1;
        }
    }
    return crc;
}

/***************************************************************************
* Function Name         : ProtocolCheck
* Description           : 检查协议是否合法
* Parameter             :Data:协议数据包 DataLen:数据包长度
* Return                :合法:1,非法:-1
* Note                  : NULL
***************************************************************************/
uint8  ProtocolCheck(uint8 *Data,uint16 DataLen){
  
  uint8 crc = 0;
//  1.检查协议头
  if(Data[0] != HEAD){
    printf("Protocol Head Error!\r\n");
    return 0;
  }
//  2.检查协议尾
  if(Data[DataLen-1] != TAIL){
    printf("Protocol Tail Error!\r\n");
    return 0;
  }
//  3.校验CRC
  
  crc = crc8(&Data[1],DataLen-3);
  if(crc != Data[DataLen-2]){
    printf("Protocol CRC Error!\r\n");
    return 0;
  }
  return 1;

}
/***************************************************************************
* Function Name         : 
* Description           : 协议处理
* Parameter             :
* Return                :
* Note                  :
***************************************************************************/
void ProtocolParse(uint8 *Data,uint16 DataLen){

  if(ProtocolCheck(Data,DataLen) == 0){
    return;
  
  }
  switch(Data[3]){
  case 0x0:
		SensorData[0] = Data[5];
	  SensorData[1] = Data[6];
    break;
  case 0x01:
    SensorData[2] = Data[5];
    break;
  default:
    break;
  
  }
 


}
/***************************************************************************
* Function Name         : 
* Description           : 协议处理
* Parameter             :
* Return                :
* Note                  :
***************************************************************************/
uint16  CreateData(uint8 *Data){

  //检查是否有错误
  if(Data !=NULL){
    Data[0] = ProtocolData.Head;
    Data[1] = ProtocolData.NodeId>>8;  
    Data[2] = ProtocolData.NodeId;  
    Data[3] = ProtocolData.NodeType;  
    Data[4] = ProtocolData.DataLen; 
		Data[5] = ProtocolData.Data; 
    Data[6] = crc8(&Data[1],5);   
    Data[7] = ProtocolData.Tail;
    return 8;  
  }
  else{
    printf("CreateData Error!\r\n");
    return 0;
  }

}


void FanControl(uint8_t Status){
	
		uint8_t DataBuff[10];
		uint8_t DataLen = 0;

		ProtocolData.Data = Status;
		DataLen = CreateData(DataBuff);
		HAL_UART_Transmit(&huart1,DataBuff,DataLen,100);

}

三、Zigbee协调器功能开发

(一)zigbeeAPP初始化

Created with Raphaël 2.3.0 串口初始化 zigbee组网初始化

在这里插入图片描述

(二)zigbeeAPP接收数据

Created with Raphaël 2.3.0 空中事件产生 消息接收处理

(三)zigbeeAPP发送数据

Created with Raphaël 2.3.0 串口接收数据 创建串口事件 串口事件发送广播数据

四、Zigbee采集节点功能开发

(一)ZigbeeAPP初始化

Created with Raphaël 2.3.0 串口初始化 zigbee组网初始化

(二)传感器采集驱动

Created with Raphaël 2.3.0 GPIO初始化 温湿度传感器采集(DHT11)

(三)传感器数据定时发送

Created with Raphaël 2.3.0 定时发送触发 定时发送处理

五、Zigbee控制节点功能开发

(一)风扇控制驱动

在这里插入图片描述

Created with Raphaël 2.3.0 GPIO初始化 风扇控制

(二)风扇控制解析

Created with Raphaël 2.3.0 空中数据事件 空中数据解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2338513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络期中复习笔记(自用)

复习大纲 –第一章 概述 计算机网络的组成 网络边缘&#xff1a;主机和网络应用程序&#xff08;又称为“端系统”&#xff09; 端系统中运行的程序之间的通信方式可划分为两大类&#xff1a; 客户/服务器方式&#xff08;C/S方式&#xff09; 对等方式&#xff08;P2P方式…

14.Chromium指纹浏览器开发教程之WebGL指纹定制

WebGL指纹概述 当在浏览器打开的网页上浏览内容时&#xff0c;看到的大多是平面的、静态的图像和文字。但是有时想要在网页上看到更加生动、立体的图像&#xff0c;如3D游戏、虚拟现实应用等。这时&#xff0c;就需要用到WebGL。 简单来说&#xff0c;WebGL&#xff08;Web G…

GitHub SSH连接终极解决方案

GitHub SSH连接终极解决方案&#xff1a;443端口修改多场景故障排查指南 一、问题现象速查 当开发者执行以下命令时出现连接异常&#xff1a; ssh -T gitgithub.com常见报错类型&#xff1a; 经典端口阻塞ssh: connect to host github.com port 22: Connection refused密钥验…

每日算法【双指针算法】(Day 1-移动零)

双指针算法 1.算法题目&#xff08;移动零&#xff09;2.讲解算法原理3.编写代码 1.算法题目&#xff08;移动零&#xff09; 2.讲解算法原理 数组划分&#xff0c;数组分块&#xff08;快排里面最核心的一步&#xff09;只需把0改为tmp 双指针算法&#xff1a;利用数组下标来…

B端管理系统:企业运营的智慧大脑,精准指挥

B端管理系统的定义与核心功能 B端管理系统&#xff08;Business Management System&#xff09;是专门设计用于支持企业内部运作和外部业务交互的一套软件工具。它集成了多种功能模块&#xff0c;包括但不限于客户关系管理(CRM)、供应链管理(SCM)、人力资源管理(HRM)以及财务管…

使用Java基于Geotools的SLD文件编程式创建与磁盘生成实战

前言 在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;地图的可视化呈现至关重要&#xff0c;而样式定义语言&#xff08;SLD&#xff09;文件为地图元素的样式配置提供了强大的支持。SLD 能够精确地定义地图图层中各类要素&#xff08;如点、线、面、文本等&#x…

Git 命令速查手册

听说用美图可以钓读者&#xff1f; 一、基础操作核心命令 1. 仓库初始化与克隆 命令作用示例git init创建新仓库git init my-projectgit clone克隆远程仓库git clone [https://github.com/user/repo.git](https://github.com/user/repo.git)git remote add关联远程仓库git re…

网络编程 - 4 ( TCP )

目录 TCP 流套接字编程 API 介绍 SeverSocket Socket 用 TCP 实现一个回显服务器 服务端 客户端 运行调试 第一个问题&#xff1a;PrintWriter 内置的缓冲区 - flush 刷新解决 第二个问题&#xff1a;上述代码中&#xff0c;需要进行 close 操作吗&#xff1f; 第三…

OSPF综合实验(HCIP)

1&#xff0c;R5为ISP&#xff0c;其上只能配置Ip地址&#xff1b;R4作为企业边界路由器&#xff0c; 出口公网地址需要通过ppp协议获取&#xff0c;并进行chap认证 2&#xff0c;整个OSPF环境IP基于172.16.0.0/16划分&#xff1b; 3&#xff0c;所有设备均可访问R5的环回&…

真实波幅策略思路

该策略是一种基于ATR&#xff08;Average True Range&#xff09;指标的交易策略&#xff0c;主要用于期货市场中的日内交易。策略的核心思想是利用ATR指标来识别市场的波动范围&#xff0c;并结合均线过滤来确定买入和卖出的时机。 交易逻辑思维 1. 数据准备与初始化 - 集合竞…

leetcode 674. Longest Continuous Increasing Subsequence

目录 题目描述 第一步&#xff0c;明确并理解dp数组及下标的含义 第二步&#xff0c;分析明确并理解递推公式 第三步&#xff0c;理解dp数组如何初始化 第四步&#xff0c;理解遍历顺序 代码 题目描述 这是动态规划解决子序列问题的例子。与第300题的唯一区别就是&#…

STM32 外部中断EXTI

目录 外部中断基础知识 STM32外部中断框架 STM32外部中断机制框架 复用功能 重映射 中断嵌套控制器NVIC 外部中断按键控制LED灯 外部中断基础知识 STM32外部中断框架 中断的概念&#xff1a;在主程序运行过程中&#xff0c;出现了特点的中断触发条件&#xff0c;使得…

Linux:基础IO---动静态库

文章目录 1. 动静态库前置知识1.1 动静态库知识回顾1.2 什么是动静态库 2. 动静态库2.1 站在库的制作者的角度2.2 站在库的使用者的角度2.3 动态库是怎么被加载的&#xff08;原理&#xff09; 序&#xff1a;上一篇文章我们从认识到理解&#xff0c;从理解到实现场景&#xff…

深度学习-torch,全连接神经网路

3. 数据集加载案例 通过一些数据集的加载案例&#xff0c;真正了解数据类及数据加载器。 3.1 加载csv数据集 代码参考如下 import torch from torch.utils.data import Dataset, DataLoader import pandas as pd ​ ​ class MyCsvDataset(Dataset):def __init__(self, fil…

Codex CLI - 自然语言命令行界面

本文翻译整理自&#xff1a;https://github.com/microsoft/Codex-CLI 文章目录 一、关于 Codex CLI相关链接资源 二、安装系统要求安装步骤 三、基本使用1、基础操作2、多轮模式 四、命令参考五、提示工程与上下文文件自定义上下文 六、故障排查七、FAQ如何查询可用OpenAI引擎&…

实现窗口函数

java 实现窗口函数 public class SlidingWin {public static void main(String[] args) {SlidingWin slidingWin new SlidingWin();double v slidingWin.SlidWin(2);System.out.println(v);}public double SlidWin(int k){int [] array new int[]{2,4,5,6,9,10,12,23,1,3,8…

清华《数据挖掘算法与应用》K-means聚类算法

使用k均值聚类算法对表4.1中的数据进行聚类。代码参考P281。 创建一个名为 testSet.txt 的文本文件&#xff0c;将以下内容复制粘贴进去保存即可&#xff1a; 0 0 1 2 3 1 8 8 9 10 10 7 表4.1 # -*- coding: utf-8 -*- """ Created on Thu Apr 17 16:59:58 …

MATLAB - 小车倒立摆的非线性模型预测控制(NMPC)

系列文章目录 目录 系列文章目录 前言 一、摆锤/小车组件 二、系统方程 三、控制目标 四、控制结构 五、创建非线性 MPC 控制器 六、指定非线性设备模型 七、定义成本和约束 八、验证非线性 MPC 控制器 九、状态估计 十、MATLAB 中的闭环仿真 十一、使用 MATLAB 中…

HAL库配置RS485+DMA+空闲中断收发数据

前言&#xff1a; &#xff08;1&#xff09;DMA是单片机集成在芯片内部的一个数据搬运工&#xff0c;它可以代替单片机对数据进行传输、存储&#xff0c;节约CPU资源。一般应用场景&#xff0c;ADC多通道采集&#xff0c;串口收发&#xff08;频繁进入接收中断&#xff09;&a…

【java实现+4种变体完整例子】排序算法中【计数排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

以下是计数排序的详细解析&#xff0c;包含基础实现、常见变体的完整代码示例&#xff0c;以及各变体的对比表格&#xff1a; 一、计数排序基础实现 原理 通过统计每个元素的出现次数&#xff0c;按顺序累加得到每个元素的最终位置&#xff0c;并填充到结果数组中。 代码示…