# znjg **Repository Path**: comptart/znjg ## Basic Information - **Project Name**: znjg - **Description**: 基于STM32的智能浇灌系统 - **Primary Language**: C - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-11-23 - **Last Updated**: 2024-03-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于STM32的智能浇灌系统 #### 介绍 基于STM32的智能浇灌系统 --- 系统分为两部分:主机与客机 客机上采集需要的数据,通过zigbee传回主机。主机负责数据的处理与显示。 --- **主程序循环任务调用系统:** ```c if(queue_head(&mission)) { switch(mission.m_state){ case state_OK://正常显示数据 lcd_show_all(); break; case state_ERROR://当没有数据或数据过旧的时候的处理 lcd_show_nodata(); break; case state_REGISTER: lcd_show_state(state_REGISTER,0); callForResister();//请求注册 lcd_show_state(state_REGISTER,1); break; case state_ACTIVE: executeOrder(mission); break; case state_INACTIVE: executeOrder(mission); break; case state_WATERING: executeOrder(mission); break; default: break; } ``` 所有的任务会写入一个队列进行排队,循环中不断提取出一个任务进行执行。 其中,为了避免队列节点申请反复调用malloc导致内存碎片化的情况,这里采用的队列结构为提前分配的定容结构,存在等待执行任务上限。 --- **注册系统模块:** 客机初次启动时会发送一个注册请求,向主机申请一个注册号。主机接受到注册请求后寻找一个未被注册的号码,发送给客机,这时开启一个等待消息返回循环,等待客机回复确认收到指令。确认收到指令后主机将注册号写入内存,客机正式开始采集数据。 --- 客机数据采集模块: 客机的数据采集暂定为温度、湿度、土壤湿度。程序执行部分由中断触发。 ```c uint8_t data[10];//记录传感器获取的数据 int tem_t=0; void GTIM_TIMX_INT_IRQHandler(void) { /* 没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */ if(__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET) { if(dht11_read_data(&data[tem],&data[humidity])) { data[state]=state_OK; } else { //如果数据读取失败则清空数据,标记错误消息 data[tem]=0; data[humidity]=0; } uint16_t adcx=0; adcx = adc_get_result_average(ADC_ADCX_CHY, 10); float shidu=(float)(4098-adcx)/(4096-1200)*100; //printf("%f\n",shidu); data[soil_humidity]=(uint8_t)shidu; //printf("%d\n",data[soil_humidity]); /* 干燥时是4096,完全湿润时是1200 (4096-adcx)/(4096-1200)*100 */ //数据检测完毕之后向队列中插入一个发送消息任务 if(tem_t>=2){ queue_push(message[code],data[state]); tem_t=0; }else tem_t++; __HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */ } } ``` 空气温湿度采集模块为DHT11直接读取数据: ```c uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i; dht11_reset();//从这里开始主机宣布要接收数据,其他时间并不会有数据进入,防止数据错位 //dht发送一次数据包括5个数据温度的整数小数,湿度的整数小数,和一个校验和=温度湿度之和 if (dht11_check() == 0) { for (i = 0; i < 5; i++) { buf[i] = dht11_read_byte(); } if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { *humi = buf[0]; *temp = buf[2]; } } else { return 0; } return 1; } ``` 土壤湿度模块为YL-69读取电压: ```c uint32_t adc_get_result(uint32_t ch) { adc_channel_set(&g_adc_handle , ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5); /* 设置通道,序列和采样时间 */ HAL_ADC_Start(&g_adc_handle); /* 开启ADC */ HAL_ADC_PollForConversion(&g_adc_handle, 10); /* 轮询转换 */ return (uint16_t)HAL_ADC_GetValue(&g_adc_handle); /* 返回最近一次ADC1规则组的转换结果 */ } ``` 读取adc模拟信号之后数据还原,结果实验后确认完全干燥时数据为4096,完全湿润时为1200,相除得出比例即为读取出的土壤湿度百分比: ```c uint16_t adcx=0; adcx = adc_get_result_average(ADC_ADCX_CHY, 10); float shidu=(float)(4098-adcx)/(4096-1200)*100; //为了避免4096-adcx=0的情况,被减数稍微偏大些许 //printf("%f\n",shidu); data[soil_humidity]=(uint8_t)shidu; /* 干燥时是4096,完全湿润时是1200 (4096-adcx)/(4096-1200)*100 */ ``` --- **主机数据显示模块:** 分为三部分:当前执行中的功能、来自客机采集来的数据、当前主机上确认注册的客机。 ```javascript char lcd_show_state_message[10][10]={ "OK", "REGISTER", "ERROR", "ELSE", "CALLBACK", "ACTVIE", "INACTIVE", "WARTERING"};//对应message.h中的state枚举 //显示任务运行状态,上次运行的内容 //参数包括,当前任务,任务处理结果(0:running,1:success,2:faild) char past_state[30];//显示上一次执行的任务,注意大小 void lcd_show_state(uint8_t state,uint8_t result){ char show_state[30]; switch(result) { case 0: sprintf(show_state,"%s %s ",lcd_show_state_message[state],"running"); break; case 1: sprintf(show_state,"%s %s ",lcd_show_state_message[state],"success"); break; case 2: sprintf(show_state,"%s %s ",lcd_show_state_message[state],"faild"); break; default: sprintf(show_state,"%s","ERROE "); break; } lcd_show_string(40,20,300,32,32,past_state,RED); lcd_show_string(40,60,300,32,32,show_state,RED); sprintf(past_state,"%s ",show_state); } ``` ```javascript /* 用于显示客机上采集到的数据 */ uint8_t client_data[20]={0};//现在这里莫名其妙爆内存可能?数据初值貌似不是0 void lcd_show_clientdata(){ uint8_t show_size=40;//字体大小 uint8_t show_top=120;//显示框上部 char show_message[20]; sprintf(show_message,"CODE:%d",client_data[code]); lcd_show_string(40,show_top,240,32,32,(char*)show_message,RED); show_top+=show_size; sprintf(show_message,"humidity:%d",client_data[humidity]); lcd_show_string(40,show_top,240,32,32,(char*)show_message,RED); show_top+=show_size; sprintf(show_message,"tem:%d",client_data[tem]); lcd_show_string(40,show_top, 240,32,32,(char*)show_message,RED); show_top+=show_size; sprintf(show_message,"soil_humidity:%d ",client_data[soil_humidity]); lcd_show_string(40,show_top, 300,32,32,(char*)show_message,RED); show_top+=show_size; } ``` ```javascript /* 用于展示目前主机上已经持有的数据,目前想到的只有一个已经注册的客机 */ void lcd_show_selfdata(){ uint8_t show_size=40;//字体大小 uint16_t show_top=520;//显示框上部 lcd_show_string(40,show_top,240,32,32,"CHOOSING:",RED); lcd_show_num(216,show_top,choosing,2,32,RED); show_top+=show_size; lcd_show_string(40,show_top,240,32,32,"register: ",RED);//当前显示的客机号 show_top+=show_size; //实际上觉得这个做法比直接lcd显示更加消耗资源 char show_message[30]="no connect"; int length = 0; int sign=0; for(int i = 0; i < sub_num; i++) { if(subClient[i] != 0) { sign=1; length += snprintf(show_message + length, sizeof(show_message) - length, "%s%d", (length > 0 ? "," : ""), i); } } if(sign) lcd_show_string(40,show_top,240,32,32," ",RED); lcd_show_string(40,show_top,240,32,32,show_message,RED); show_top+=show_size; } ``` --- **通讯模块:** 定义一个专门的消息数组message ```c typedef enum { code = 0,//设备号 state = 1,//操作请求 tem = 2,//温度 humidity = 3,//湿度 soil_humidity = 4//土壤湿度 }dataNumber; typedef enum { state_OK = 0x00U,//一切正常,传出各项数值 state_REGISTER = 0x01U,//请求客机号 state_ERROR = 0x02U,//客机设备出现异常 state_ELSE = 0x03U,//其他 state_CALLBACK = 0x04U,//用来处理一些需要返回的消息 state_ACTIVE=0x05U,//启用呼叫 state_INACTIVE=0X06U,//停用呼叫 state_WATERING=0X07U//启用电机浇水 } STATE;//进行功能请求 ``` 每次对指令的发送就是先对这个数组的赋值,然后将数组信息发送至串口 定义一个简单的串口通讯协议:以换行键与回车键作为整个消息的结束 接受到的消息会在processingData()函数中直接分配好任务后等待下一组消息的到来 ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART_UX) /* 如果是串口1 */ { if ((g_usart_rx_sta & 0x8000) == 0) /* 接收未完成 */ { if (g_usart_rx_sta & 0x4000) /* 接收到了0x0d(即回车键) */ { if (g_rx_buffer[0] != 0x0a) /* 接收到的不是0x0a(即不是换行键) */ { g_usart_rx_sta = 0; /* 接收错误,重新开始 */ } else /* 接收到的是0x0a(即换行键) */ { g_usart_rx_sta |= 0x8000; /* 接收完成了 */ processingData(); g_usart_rx_sta=0; HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); return; } } else /* 还没收到0X0d(即回车键) */ { if (g_rx_buffer[0] == 0x0d) g_usart_rx_sta |= 0x4000; else { g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0]; g_usart_rx_sta++; if (g_usart_rx_sta > (USART_REC_LEN - 1)) { g_usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */ } } } } HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); } } ```