记录|2024.6课设
前言
作为我校的优良传统,本次小学期布置的课设任务是······
其实不重要,毕竟允许使用核心板、现成模块,整体的难度其实比较低,无非是读几个adc,运行几个灯\电机,学不到什么东西,之前学习了lvgl用esp32做了个手表,那这次就用stm32作为主控吧。
任务:移植lvgl作为用户交互界面,并适配自购的电容触控板,使用SquareLine Studio设计ui,并编写合适的回调函数处理相关任务。
原件清单:
- 立创开发板·天空星stm32f407vet6
- 320x240,2.4寸tftlcd(ili9341)
- 2.4寸电容触摸屏(FT6636U)
模块调试
在移植上层应用LVGL前,我们需要先保证底层驱动是正常的,即成功点亮屏幕、读取触控坐标
屏幕
本次课设我使用的屏幕模块其驱动ic是ili9341,四线SPI(SCL、SDA、DC、CS),另有RES复位引脚和BLK背光控制引脚,直接使用商家的例程即可点亮屏幕,在此我只介绍将商家的软件SPI换为硬件SPI+DMA1
2
3
4
5
6
7
8
9// 关于LCD接线--------------------------------------------------------
// GND 电源地
// VCC 5V或3.3v电源
// SCL PA5(SCLK)
// SDA PA7(MOSI)
// RES PA3
// DC PA2
// CS PA4
// BLK PA1
初始化
首先是初始化部分:
- 初始化四个GPIO:DC、CS、RES、BLK(lcd_init.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22void LCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能PORTA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|
GPIO_Pin_2|
GPIO_Pin_4|
GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOA, GPIO_Pin_3|
GPIO_Pin_2|
GPIO_Pin_4|
GPIO_Pin_1 );
} - 初始化SPI控制器,并接管SCLK、MOSI引脚(spi.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43/*
功能:初始化SPI1控制器
入口参数:无
返回参数:无
*/
void SPI1_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
// NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能PORTA时钟
/* 设置引脚复用 */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
/* 配置SPI引脚引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* FLASH_SPI 模式配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 配置为主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8位数据
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 极性相位
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件cs
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // SPI时钟预调因数为2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
/* 使能 SPI */
SPI_Cmd(SPI1, ENABLE);
} - 初始化DMA,打通内存到spi控制器的通路(dma.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48/*
功能:初始化DMA
入口参数:DMA通道,第几个通道,外设地址,内存地址,数据长度
返回参数:无
*/
void MYDMA_Config(DMA_Stream_TypeDef* DMAx_Streamx,u8 Channel_x,u32 cpar,u32 cmar,u32 Buffer_LEN)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
if((u32)DMAx_Streamx>(u32)DMA2)//通过偏移量判断DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMAx_Streamx);
DMA_InitStructure.DMA_Channel = Channel_x*(uint32_t)0x02000000; //通道选择
DMA_InitStructure.DMA_Memory0BaseAddr = cmar; // 设置内存基地址
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; // 设置外设基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; // 设置传输方向为从内存到外设
DMA_InitStructure.DMA_BufferSize = Buffer_LEN; // 设置缓冲区大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 禁止外设地址增量
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 允许内存地址增量
DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_HalfWord; // 外设数据大小为半字(16bit)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 内存数据大小为半字(16bit)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 设置传输模式为普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 设置传输优先级为中等
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // 禁用FIFO模式
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; // FIFO阈值设置为满
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 内存突发传输设置为单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 外设突发传输设置为单次传输
DMA_Init(DMAx_Streamx, &DMA_InitStructure); // 初始化DMA配置
SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE);
DMA_ITConfig(DMAx_Streamx, DMA_IT_TC, ENABLE);//开启DMA的中断
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream3_IRQn; // DMA 中断对应的中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 中断优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 中断子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(DMAx_Streamx,ENABLE);
}调用
- 硬件SPI发送数据(lcd_init.c)
直接将原来软件的部分改为硬件的即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void LCD_Writ_Bus(u8 dat)
{
LCD_CS_Clr();// 选中器件
//等待发送缓冲区为空
while(RESET == SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) );
//通过SPI1发送一个字节数据
SPI_I2S_SendData(SPI1, dat);
//等待接收缓冲区不空标志
while(RESET == SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) );
SPI_I2S_ReceiveData(SPI1);
LCD_CS_Set();// 释放器件
} - DMA向SPI控制器搬运数据(dma.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/*
功能:要求DMA搬运数据
入口参数:DMA通道,内存地址,数据长度
返回参数:无
*/
void MYDMA_Enable_T(DMA_Stream_TypeDef *DMAx_Streamx,void *buf ,u32 Buffer_LEN)
{
LCD_CS_Clr(); // 选中器件
LCD_DC_Set(); // DC脚拉高为数据
DMA_Cmd(DMAx_Streamx,DISABLE); // 为了不冲突,开启传输前失能该DMA
while (DMA_GetCmdStatus(DMAx_Streamx)!= DISABLE){} // 等待
DMA2_Stream3->M0AR = (unsigned int)buf; // 设定内存地址
DMA_SetCurrDataCounter(DMAx_Streamx,Buffer_LEN); // 设定数据长度
DMA_Cmd(DMAx_Streamx,ENABLE); // 开始传输
}触控
商家给的示例代码为HAL库,利用宏定义进行简单的修改便可改为标准库:1
2
3
4// 关于触控接线----------------------------------------------------------------
// SCL PB5
// SDA PB4
// ----------------------------------------------------------------在具体的使用中,可能出现触控坐标相反的情况,直接在读取的地方更改即可:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
171
2
3
4ft6336u_read_touch_pos(&touch);
(*x) =touch.touch0_y;
(*y) =240-touch.touch0_x; // 相当于取反了移植lvgl
主工程
由于使用了DMA传输数据,考虑大小端的问题,需要在lv_conf.h中使能高低位交换1
屏幕
对于屏幕的移植很简单,我们可以直接使用官方示例文件lv_port_disp_template.c,里面有很详细的代码例程,代码也很简单易懂,文档也预留了地方为我们添加初始化和刷新屏幕,在此略过不讲,然后记得通知lvgl绘图已完成:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void DMA2_Stream3_IRQHandler(void)
{
// 检查传输完成中断标志位
if (DMA_GetITStatus(DMA2_Stream3, DMA_IT_TCIF3)!=RESET)
{
// 清除传输完成中断标志位
DMA_ClearITPendingBit(DMA2_Stream3, DMA_IT_TCIF3);
// 读取SPI1的数据寄存器,这通常是为了清除相关的中断标志位
// 并确保所有数据都已经由SPI硬件发送完成
volatile uint8_t temp = SPI1->DR;
// 通知LVGL绘图操作已完成
lv_disp_flush_ready(disp_drv_p);
}
}
调用传输后,lvgl在前台渲染另一缓冲区,dma在后台传输当前缓冲区,传输完成后调用中断通知lvgl绘图已经完成,lvgl交换两个缓冲区的指针,开始下一次的刷新,周而复始。
触控
对于触控的移植,同样使用lv_port_indev_template.c,我们只保留触控板相关的声明和函数,然后把触控的初始化和读取代码缝合上去即可,在此略过不讲。
Ui代码
配置完基本的图形化界面后,需要继续配置回调函数的功能,来实现相关的功能。
代码结构
在Squareline Studio中生成的代码一般有以下结构:1
2
3
4
5
6
7├── assets
├── fonts
├── screens
│ └── screen1.c
│ └── screen2.c
├── ui.c
└── ui.h
- 在ui.c中,会定义屏幕所需的变量,以及屏幕动作的回调函数,如左滑右滑去往哪几个屏幕
- 在ui.h中,则负责函数的声明与变量的extern
- screens文件夹中的各个.c文件则定义了各自屏幕具体的部件逻辑
背光控制
对于我使用的屏幕,只需控制BLK引脚的占空比即可实现背光亮度的控制,于是只需在ui中画一个拖动条,再在回调函数中设置占空比即可。
- 在本次课设中,我接入BLK引脚的是PA1,查阅手册可知,其复用功能为TIM5_CH2,其初始化函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35void TIM5_PWM_Init(u32 arr,u32 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); //TIM5时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM5); //GPIOA1复用为定时器5
TIM_TimeBaseStructure.TIM_Prescaler=psc-1; //定时器分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);//初始化定时器5
//初始化TIM5 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性低
TIM_OC2Init(TIM5, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM5 OC2
TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable); //使能TIM5在CCR2上的预装载寄存器
TIM_ARRPreloadConfig(TIM5,ENABLE);//ARPE使能
TIM_Cmd(TIM5, ENABLE); //使能TIM5
} - 而对于ui的设定如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
lv_obj_t * slider_label;
static void slider_event_cb(lv_event_t * e)
{
lv_obj_t * slider = lv_event_get_target(e);
char buf[8];
int brightness=0;
lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
lv_label_set_text(slider_label, buf);
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); // 设定部件的位置与偏移量
brightness = (float)lv_slider_get_value(slider)/100.0f*450 + 50; // 将0~100映射为50~500(500对应100%占空比)
TIM_SetCompare2(TIM5,brightness);
}
void ui_Screen2_screen_init(void)
{
ui_Screen2 = lv_obj_create(NULL);
lv_obj_clear_flag( ui_Screen2, LV_OBJ_FLAG_SCROLLABLE ); /// Flags
lv_obj_t * bright_label = lv_label_create(ui_Screen2);
lv_obj_set_width( bright_label, LV_SIZE_CONTENT);
lv_obj_set_height( bright_label, LV_SIZE_CONTENT);
lv_obj_align(bright_label, LV_ALIGN_CENTER, 0, -50); // 将标签居中对齐
lv_label_set_text(bright_label,"Brightness");
lv_obj_set_style_text_color(bright_label, lv_color_hex(0x879FA5), LV_PART_MAIN | LV_STATE_DEFAULT );
lv_obj_set_style_text_opa(bright_label, 255, LV_PART_MAIN| LV_STATE_DEFAULT);
lv_obj_set_style_text_font(bright_label, &lv_font_montserrat_30, LV_PART_MAIN| LV_STATE_DEFAULT);
lv_obj_t * slider = lv_slider_create(ui_Screen2);
lv_obj_center(slider);
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
/*Create a label below the slider*/
slider_label = lv_label_create(ui_Screen2);
lv_label_set_text(slider_label, "0%");
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
lv_obj_add_event_cb(ui_Screen2, ui_event_Screen2, LV_EVENT_ALL, NULL);
}按钮控制
与滑动条一样,按钮一样有对应的回调函数,官网有很详尽的例程。仪表盘
同上。
普通版本
除了加强自我要求的版本,需要做一个更简化的版本,供队友调试与使用
- stm32c6t6
- stm32c8t6
- 0.96寸 IIC-OLED
但简单如此也有踩坑的地方:外设的选择错误,相较于C8T6,C6T6仅有32kb的flash,10kb的sram,没有TIM4,UART3 ,而画PCB的时候刚好没考虑到这个,用了串口3;同样的问题在移植FreeRTOS也出现过,systick需要作为心跳给FreeRTOS使用,所以需要另选一个时基源,犯错:选了不存在的TIM4
经验
在使用普通版本与主机ESP32通信时,ESP32端如需发送16进制码,使用:1
Serial.printf("%c",0xff);
而不是1
Serial.print(0xff,HEX);
第二个会以字符形式(ASCII码)发送HEX数据
主控
使用esp32.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
typedef enum
{
HOST = 0x01,
SLAVE1 = 0x02,
SLAVE2 = 0x03,
SLAVE3 = 0x04,
SLAVE4 = 0x05
}
rxDataDingWei;
typedef enum{
EFFECT_LJ = 0X01,//连接
EFFECT_DK = 0X00,//断开
EFFECT_DA = 0X03//传输
}rxDataGongNeng;
uint8_t keyFlag_1 = 1;//总软开关
uint8_t keyFlag_2 = 1;
uint8_t keyFlag_3 = 1;
uint8_t keyFlag_4 = 1;
uint8_t keyFlag_5 = 1;
// 构造对象 连接到I2C(SDA、SCL引脚)的SSD1306声明
Adafruit_SSD1306 OLED(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Ticker ticker; //定时器的类
int my_num=0;
void time_function(void)
{
my_num++;
if(my_num==1)
{
Serial2.write(0x11);
}
else if(my_num==2)
{
Serial2.printf("@BUMP\r\n");
}
else if(my_num==3)
{
Serial2.write(0x33);
}
else if(my_num==4)
{
Serial2.write(0x44);
my_num=0;
}
}
void setup()
{
Serial.begin(115200);//连到esp32tpc
Serial2.begin(115200);//连到esp32到蓝牙mx02
Serial.println();
Serial.println();
// Serial.println("hi");
ticker.attach(0.1, time_function);//定时器,每10毫秒执行一次time_function函数
// OLED初始化
OLED.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
// OLED清除显示
OLED.clearDisplay();
// OLED设置光标位置
OLED.setCursor(40, 20);
// 设置字体颜色
OLED.setTextColor(SSD1306_WHITE);
// 显示字符串内容
OLED.println("Welcome!");
OLED.display();
delay(3000);
OLED.clearDisplay();
// 初始化时显示静态内容
OLED.setCursor(0, 0);
OLED.println("device1:");
OLED.setCursor(0, 15);
OLED.println("device2:");
OLED.setCursor(0, 30);
OLED.println("device3:");
OLED.setCursor(0, 45);
OLED.println("device4:");
OLED.display();
}
void handleCommand(String input)
{
input.trim();
if (input.startsWith("2,")) {
String command = input.substring(2);
if (command.equals("打开")) {
Serial.println("Executing open operation for device 2...");
Serial2.printf("%c",0x21);//打开加热设备
Serial2.printf("%c",0x22);//打开通风设备
Serial2.printf("%c",0x24);//打开加湿设备
keyFlag_2 = 1;
} else if (command.equals("关闭"))
{
Serial.println("Executing close operation for device 2...");
Serial2.printf("%c",0x23);//关闭设备
Serial2.printf("%c",0x26);//关闭设备
keyFlag_2 = 0;
}
} else if (input.startsWith("3,")) {
String command = input.substring(2);
if (command.equals("打开")) {
Serial.println("Executing open operation for device 3...");
Serial2.print("@BUMP_ON\r\n");
keyFlag_3 = 1;
} else if (command.equals("关闭")) {
Serial.println("Executing close operation for device 3...");
Serial2.print("@BUMP_OFF\r\n");
keyFlag_3 = 0;
}
}
else if (input.startsWith("1,")) {
String command = input.substring(2);
if (command.equals("打开")) {
Serial.println("Executing open operation for host...");
keyFlag_1 = 1;
}
else if (input.startsWith("4,")) {
String command = input.substring(2);
if (command.equals("打开")) {
Serial.println("Executing open operation for device 4...");
Serial2.printf("%c",0x41);//打开补光设备
keyFlag_4 = 1;
} else if (command.equals("关闭")) {
Serial.println("Executing close operation for device 4...");
Serial2.printf("%c",0x42);//打开补光设备
keyFlag_4 = 0;
}
}else if (input.startsWith("5,")) {
String command = input.substring(2);
if (command.equals("打开")) {
Serial.println("Executing open operation for device 5...");
keyFlag_5 = 1;
} else if (command.equals("关闭")) {
Serial.println("Executing close operation for device 5...");
Serial2.printf("%c",0x52);//打开换气设备
keyFlag_5 = 0;
}
}else if (input.startsWith("1,")) {
String command = input.substring(2);
if (command.equals("打开")) {
Serial.println("Executing open operation for host...");
keyFlag_1 = 1;
} else if (command.equals("关闭")) {
Serial.println("Executing close operation for host...");
keyFlag_1 = 0;
}
}
// 添加其他设备的命令处理逻辑
}
}
void loop()
{
if (Serial.available())
{
String input = Serial.readStringUntil('\n');
handleCommand(input);
}
// 从UART2读取输入数据
if(Serial2.available() >= 14)
{
uint8_t frame[14];
Serial2.readBytes(frame, 14);
for(int n=0;n<14;n++)
{
Serial.print(frame[n],HEX);
}
Serial.println();
if (frame[0] == HEADER_ID && frame[13] == FOOT_ID)
{
switch (frame[1])
{
case SLAVE1:
// 温湿度
if (frame[2] == EFFECT_DA && keyFlag_2 == 1 && keyFlag_1 == 1)
{
float temperature = frame[3] * 10 + frame[4] + frame[6] / 10.0 + frame[7] / 100.00;
float humidity = frame[8] * 10 + frame[9] + frame[11] / 10.0 + frame[12] / 100.00;
Serial.print("Received Temperature: ");
Serial.print(temperature);
Serial.print(" C, Humidity: ");
Serial.print(humidity);
Serial.println(" %");
if(temperature < 20)//温度下限
{
Serial2.printf("%c",0x21);//打开加热设备
}
if(temperature > 32)//温度上限
{
Serial2.printf("%c",0x22);//打开通风设备
}
if(temperature >= 20 && temperature <=32)//温度正常
{
Serial2.printf("%c",0x23);//关闭设备
}
if(humidity < 50)//湿度下限
{
Serial2.printf("%c",0x24);//打开加湿设备
}
if(humidity > 95)//湿度上限
{
Serial2.printf("%c",0x25);
}
if(humidity>=50 && humidity <= 95)//湿度上限
{
Serial2.printf("%c",0x26);//关闭设备
}
// 覆盖旧的数据区域
OLED.fillRect(50, 0, 78, 8, SSD1306_BLACK);
OLED.fillRect(90, 0, 38, 8, SSD1306_BLACK);
// 显示新的数据
OLED.setCursor(50, 0);
OLED.print(temperature);
OLED.setCursor(90, 0);
OLED.print(humidity);
OLED.display();
}
break;
case SLAVE2:
// 土壤湿度
if (frame[2] == EFFECT_DA && keyFlag_3 ==1 && keyFlag_1 == 1)
{
float turang_humi = frame[3] * 10 + frame[4] + frame[6] / 10.0 + frame[7] / 100.00;
Serial.print("turang_humi: ");
Serial.print(turang_humi);
Serial.println(" %");
Serial.println(turang_humi);
if(turang_humi < 30)//土壤湿度下限
{
Serial2.print("@BUMP_ON\r\n");//打开浇灌设备
}
else
{
Serial2.print("@BUMP_OFF\r\n");//关闭浇灌设备
}
// 覆盖旧的数据区域
OLED.fillRect(50, 15, 78, 8, SSD1306_BLACK);
// 显示新的数据
OLED.setCursor(50, 15);
OLED.print(turang_humi);
OLED.display();
}
break;
case SLAVE3:
// 光照
if (frame[2] == EFFECT_DA && keyFlag_4 ==1 && keyFlag_1 == 1)
{
float guangzhao = frame[3] * 10 + frame[4] + frame[6] / 10.0 + frame[7] / 100.00;
Serial.print("guangzhao: ");
Serial.print(guangzhao);
Serial.println(" %");
if(guangzhao<40)
{
Serial2.printf("%c",0x41);//打开补光设备
Serial.print("补光开\r\n");
}
if(guangzhao>90)
{
Serial2.printf("%c",0x42);//打开补光设备
}
// 覆盖旧的数据区域
OLED.fillRect(50, 30, 78, 8, SSD1306_BLACK);
// 显示新的数据
OLED.setCursor(50, 30);
OLED.print(guangzhao);
OLED.display();
}
break;
case SLAVE4:
// 温室气体
if (frame[2] == EFFECT_DA && keyFlag_5 ==1 && keyFlag_1 == 1)
{
float wenshi = frame[3] * 1000 + frame[4] * 100+ frame[5] * 10 + frame[6];
Serial.print("wenshi: ");
Serial.print(wenshi);
Serial.println(" %");
if(wenshi > 420)
{
Serial2.printf("%c",0x51);//打开换气设备
}
if(wenshi < 420)
{
Serial2.printf("%c",0x52);//打开换气设备
}
// 覆盖旧的数据区域
OLED.fillRect(50, 45, 78, 8, SSD1306_BLACK);
// 显示新的数据
OLED.setCursor(50, 45);
OLED.print(wenshi);
OLED.display();
}
break;
default:
break;
}
}
}
}