# VsCode配置STM32编译调试环境
【保姆】vscode配置单片机编译调试烧录环境(以STM32为例)_哔哩哔哩_bilibili
 一、ADC采集系统
 1. ADC通道(外部电路)
 2. 功能要求
 3. 动态窗口
"动态"的含义:3秒的实时采集窗口随着时间自行移动,adc采集的值动态实时更新在3s的窗口数据内
 二、功能实现
 1. ADC解算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | uint32_t dma_buff[2][30]; float adc_value[2];
  void adc_proc()  { 	for(uint8_t i=0;i<30;i++)  	{ 		adc_value[0] += (float)dma_buff[0][i];  		adc_value[1] += (float)dma_buff[1][i]; 	}           	 	 	           	adc_value[0] = adc_value[0] / (30+1); 	adc_value[1] = adc_value[1] / (30+1); }
   | 
 
 2. LCD底层实现
 2.1 变量定义
1 2 3
   | uint8_t lcd_disp_mode; uint16_t ph_value; uint16_t pd_value;
   | 
 
参数界面
记录界面
 2.2 LCD进程
由于4T官方提供的LCD底层驱动,当显示的数据位数增加时,显示的位数会增加,但是当位数减小时,却不能对旧的数据进行清空。
所以这里用“空格”来覆盖刷新,达到位数减小显示缩减的效果。
1 2 3 4 5 6 7 8 9 10 11 12
   | void lcd_proc() {     switch(lcd_disp_mode){         case 0:             	LCD_Sprintf(Line1,"        DATA");             	LCD_Sprintf(Line3,"   R37:%d    ",(int)adc_value[0]);             	LCD_Sprintf(Line4,"   R38:%d    ",(int)adc_value[1]);             break;         case 1:                  } }
   | 
 
 #LCD背光 问题
现象:如图所示,只有在对LCD写入的片段,LCD才有正常的背景
原因:未对LCD进行初始化清屏
1 2 3 4 5 6 7
   |   system_init(); LCD_Init(); LCD_Clear(Black); LCD_SetTextColor(White); LCD_SetBackColor(Black); scheduler_init();
   | 
 
来源:lcd.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | 
 
 
 
 
 
  void LCD_Clear(u16 Color) {     u32 index = 0;     LCD_SetCursor(0x00, 0x0000);     LCD_WriteRAM_Prepare();      for(index = 0; index < 76800; index++)     {         LCD_WR_DATA(Color);     } }
 
  | 
 
 2.3 LED功能和初始化状态
 2.4 LCD底层完整代码实现
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
   | #include "bsp_system.h"
  uint8_t lcd_disp_mode;     uint16_t ph_value = 2000;  uint16_t pd_value = 1000;  uint16_t vh_value;         uint16_t vd_value;        
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  void LcdSprintf(uint8_t Line, char *format, ...) {     char String[21];                          va_list arg;                              va_start(arg, format);                    vsprintf(String, format, arg);            va_end(arg);                              LCD_DisplayStringLine(Line, String);  }
  void lcd_proc() {     switch (lcd_disp_mode)     {     case 0:          LcdSprintf(Line1, "        DATA");         LcdSprintf(Line3, "   R37:%d    ", (int)adc_value[0]);         LcdSprintf(Line4, "   R38:%d    ", (int)adc_value[1]);         break;     case 1:          LcdSprintf(Line1, "        PARA");         LcdSprintf(Line3, "   PH:%d    ", ph_value);         LcdSprintf(Line4, "   PD:%d    ", pd_value);         break;     case 2:          LcdSprintf(Line1, "        RECD");         LcdSprintf(Line3, "   VH:%d    ", vh_value);         LcdSprintf(Line4, "   VD:%d    ", vd_value);         break;     } }
   | 
 
 3. 按键底层
uwTick:在Systick(系统滴答定时器)中断中自增,可以用作单片机运行的时间戳
HAL库与Cubemx系列|Systick-系统滴答定时器详解-腾讯云开发者社区-腾讯云 (tencent.com)
 3.1 按键处理进程
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
   | uint8_t ph_pd_flag; 
 
 
 
 
 
  void key_proc(void) { 	 	key_val = key_read(); 	 	key_down = key_val & (key_old ^ key_val); 	 	key_up = ~key_val & (key_old ^ key_val); 	 	key_old = key_val;
  	if (key_down == 3) 	{ 		if (lcd_disp_mode == 1) 		{ 			ph_pd_flag ^= 1;	 		} 		else if(lcd_disp_mode == 2) 		{ 			key_tick = uwTick;  		} 	}
  	if(key_up == 3) 	{ 		if(lcd_disp_mode == 2) 		{ 			if(uwTick - key_tick > 2000) 			{ 				key_tick = 0;		 				vd_value = vh_value = 0; 			} 		} 	}
  	switch (key_down) 	{ 		case 4: 			if (++lcd_disp_mode == 3) 			{ 				lcd_disp_mode = 0; 			} 			break; 		case 1: 			if (lcd_disp_mode == 1) 			{ 				 				 				uint16_t *p = (ph_pd_flag) ? &ph_value : &pd_value; 				*p += 100;
  				if (*p > 4096) 				{ 					*p = 4096; 				} 			} 			break; 		case 2: 			if (lcd_disp_mode == 1) 			{ 				uint16_t *p = (ph_pd_flag) ? &ph_value : &pd_value; 				*p -= 100; 				if (*p == 65536 - 100) 				{ 					*p = 0; 				} 			} 			break; 	} }
   | 
 
 4. adc采集
 4.1 变量定义
1 2 3 4 5 6 7 8 9 10 11 12
   | #include "adc_app.h"
  uint32_t dma_buff[2][30]; float adc_value[2];
  #define WINDOWS_SIZE 3000 
  adc_data_t adc_buffer[BUFFER_SIZE]; int buffer_start = 0; int buffer_end = 0;
  uint8_t vd_flag;
   | 
 
 4.2 添加数据到动态串口(缓冲区)
本例中,ADC采样的环形缓冲区比较特殊,具备动态时间窗口的特性
- 和一般的环形缓冲区一样,具备头指针和尾指针的概念,环形存取数据。
 
- 缓冲区具备“时间窗口”的概念,那么就要让缓冲区中最老的数据,存在时间不能超过三秒,超过则移除(实际上是写指针移位,相当于队这个无用的数据不再进行读取,环形缓冲区中读取数据,就相当于将这个数据移除缓存区,因为索引指针不会再指向这个数据。)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | 
 
 
 
  void add_adc_data(uint32_t adc,uint32_t current_time,adc_data_t *buffer) { 	buffer[buffer_end].timestamp = current_time; 	buffer[buffer_end].adc = adc;
  	buffer_end = (buffer_end + 1) % BUFFER_SIZE;
  	if(buffer_end == buffer_start) 	{ 		buffer_start = (buffer_start + 1) % BUFFER_SIZE; 	}
  	 	while((current_time - buffer[buffer_start].timestamp > WINDOWS_SIZE)) 	{ 		buffer_start = (buffer_start + 1) % BUFFER_SIZE; 	} }
 
  | 
 
 4.3 检查缓冲区的突变
对当前窗口进行极大值,极小值的检测。
注意区分极大值,极小值和最大值最小值的区别。
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
   | 
 
 
 
  void check_adc_sudden_change(uint16_t *sudden_change_count,adc_data_t *buffer) { 	uint16_t f_max = buffer[buffer_start].adc; 	 	uint16_t f_min = buffer[buffer_start].adc;
  	int index = buffer_start;
  	while(index != buffer_end) 	{ 		 		if(buffer[index].adc > f_max) 		{ 			f_max = buffer[index].adc; 		} 		 		if(buffer[index].adc < f_min) 		{ 			f_min = buffer[index].adc; 		} 		 		index = (index + 1) % BUFFER_SIZE; 	}
  	uint16_t diff = f_max - f_min;
  	 	if(diff < pd_value) 	{ 		vd_flag = 1; 	} 	else if(vd_flag == 1) 	{ 		vd_flag = 0; 		(*sudden_change_count) ++; 	}
  	ucLed[2] = (diff > pd_value)?1:0; }
 
  | 
 
 4.4 ADC解析进程
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
   | void adc_proc()  { 	uint32_t Time_tick = HAL_GetTick();	
  	static uint8_t vh_flag;
  	 	for(uint8_t i=0;i<30;i++)  	{ 		adc_value[0] += (float)dma_buff[0][i];  		adc_value[1] += (float)dma_buff[1][i];  	}
  	adc_value[0] = adc_value[0] / (30 + 1);  	adc_value[1] = adc_value[1] / (30 + 1); 
  	add_adc_data(adc_value[0],Time_tick,adc_buffer); 	check_adc_sudden_change(&vd_value,adc_buffer);
  	if(adc_value[1] < ph_value)  	{ 		vh_flag = 0;  	} 	else if(vh_flag == 0)  	{ 		vh_flag = 1; 		vh_value++;	 	} }
   | 
 
 5. LED底层
 5.1 LED显示进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | 
 
 
 
  void led_proc(void) {          ucLed[0] = (lcd_disp_mode == 0);     ucLed[1] = adc_value[1] > ph_value ? 1 : 0;               led_disp(ucLed); }
 
  | 
 
 6. 串口通信
 6.1 串口通信进程
sscanf:
sscanf 是一个格式化输入函数,主要用于从字符串中提取数据。 
- 它按照指定的格式读取输入字符串,并将解析后的数据存储到指定的变量中。
 
- 语法:
int sscanf(const char *str, const char *format, ...) 
strcmp:
strcmp 是一个字符串比较函数,用于比较两个字符串是否相等。 
- 它返回一个整数,表示两个字符串的字典顺序。
 
- 语法:
int strcmp(const char *str1, const char *str2) 
- 返回值:
- 小于 0:
str1 小于 str2 
- 等于 0:
str1 等于 str2 
- 大于 0:
str1 大于 str2 
 
总结:
sscanf:将stream内容取出,并根据入口参数的格式化过滤内容,取出数据
strcmp:比较两个字符串的内容
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
   | void uart_proc(void) { 	if(ringbuffer_is_empty(&usart_rb)) return; 	 	ringbuffer_read(&usart_rb, usart_read_buffer, usart_rb.itemCount); 	 	uint16_t value; 	uint16_t *p = NULL; 	      	if(sscanf((const char*)usart_read_buffer,"$PD(%hu)",&value) == 1) 	{          		p = &pd_value; 	} 	else if(sscanf((const char*)usart_read_buffer,"$PH(%hu)",&value) == 1) 	{          		p = &ph_value; 	} 	else if(strcmp((const char*)usart_read_buffer,"#VH") == 0) 	{ 		printf("VH:%d\n",vh_value); 	} 	else if(strcmp((const char*)usart_read_buffer,"#VD") == 0) 	{ 		printf("VD:%d\n",vd_value); 	}	 	      	if(value < 4096) 	{          		*p = value;  	} 	      	memset(usart_read_buffer, 0, sizeof(uint8_t) * BUUFER_SIZE);	 }
   |