The frontline subsidiary has a drug detection-based project that needs to do a curve display function, because this is my shortcoming of skills, because I used to do more software applications, logic, framework, and architecture design, and my younger brother I am very proficient at the bottom level, so I gave this core function to my junior and asked him to help implement the basic library, and then I completed the functions required by the product based on his library.
Just before the project, RT-Thread launched a 基于RT-Thread Nano的Mini示波器
DIY activity. As a member of the RT-Thread community working group, I was fortunate to be able to see the production process of this project from beginning to end, and also learned LCD curve data processing and Show some thoughts.
The activity link is as follows:
[DIY Activity] Let’s build a Mini oscilloscope based on RT-Thread Nano!
The following three steps are roughly required to complete the curve display:
1. Data collection
2. Data processing
3. Data display
Not much nonsense, let's take a look at the display effect:
Strictly speaking, the SPI screen of Cubs Pie is actually not suitable for scrolling curves. First, the resolution is too low, and the speed of SPI is not high. If the curve display conditions are a little harsher, it will easily cause LCD display to flicker. Screen phenomenon, the experience is very bad, but the sensor data shows that we are still capable of achieving it.
Therefore, we need to make some basic optimizations to the screen driver:
1. Optimize LCD driver
1. Improve the screen refresh speed
Because of the need to refresh the curve, we can only find a way to increase the refresh rate of the screen as much as possible, so there is such a register in the LCD manual that can increase the refresh rate of the screen:
In the LCD driver initialization code, the default configuration of this register is 60Hz, which is the value 0x0F
/* Frame Rate Control in Normal Mode */
LCD_Write_Cmd(0xC6);
// LCD_Write_Data(0x0F); //60HZ
LCD_Write_Data(0x01); //111Hz 提升屏的刷新速度
Originally set to 0x00 to 119Hz, but after setting the LCD, the LCD screen will be black, and it will not be changed to 0x01. No specific reason has been found. Maybe this is a bug in the screen firmware, which will be used temporarily; or if a friend knows, thank you Share with me in the message area.
2. Use register to send instead
/**
* @brief LCD底层SPI发送数据函数
*
* @param data 数据的起始地址
* @param size 发送数据大小
*
* @return void
*/
static void LCD_SPI_Send(uint8_t *data, uint16_t size)
{
for(int i = 0 ; i < size ; i++)
{
*((uint8_t*)&hspi2.Instance->DR) = data[i];
while(__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1) {}
}
}
The HAL_SPI_Transmit
function sending of the HAL library will be slower, and the register sending will be faster.
2. Curve display logic
If you want to display the curve on the LCD, you may have this question:
My data may be thousands or tens of thousands. How can I convert it into a display corresponding to the screen resolution? Where does the display start? How to display?
A better idea is to define a fixed-length array, and update the data continuously to the end of the array each time, and then the data will be pushed forward continuously. This is actually what we call the fifo (ring buffer) queue, and then define A new backup buffer. Find the maximum and minimum values of the data in this backup buffer, find the scaling factor for LCD resolution, and use the backup buffer for LCD display based on the calculation result. This is based on actual conditions. The zoom is also called partial zoom. The following is the curve data structure of this routine:
#define DATA_SIZE 240
/*曲线显示区域,即相对于LCD的宽度,X轴*/
#define PLOT_DISPLAY_AREA_X 51
/*曲线显示区域,即相对于LCD的高度,Y轴*/
#define PLOT_DISPLAY_AREA_Y 210
#define LCD_X 240
#define LCD_Y 240
/*曲线处理*/
typedef struct
{
/*实时曲线数据*/
uint16_t rel_data_data[DATA_SIZE];
/*旧的曲线数据*/
uint16_t old_plot_data[DATA_SIZE];
/*新的曲线数据*/
uint16_t new_plot_data[DATA_SIZE];
} plot_data_handler ;
extern plot_data_handler plot_handler ;
Since a one-time update is required to avoid splash screens, three buffers are defined here rel_data_data
for updating real-time data, which old_plot_data
are the old processed display data and new_plot_data
the just processed display data, which is equivalent to a double buffering effect.
3. Realization of curve display
3.1 Data sampling part
Since the data buffer of the curve is empty at the beginning of the display, we need to initialize it to ensure that the curve can be displayed directly:
smoke_value = mq2_sensor_interface.get_smoke_value(&mq2_sensor_interface);
for(int i = 0 ; i < DATA_SIZE ; i++)
plot_handler.rel_data_data[i] = smoke_value ;
memcpy(plot_handler.new_plot_data, plot_handler.rel_data_data, sizeof(plot_handler.new_plot_data));
memcpy(plot_handler.old_plot_data, plot_handler.new_plot_data, sizeof(plot_handler.new_plot_data));
The next step is mentioned in the display logic, we need to have a ring buffer, constantly adding data:
smoke_value = mq2_sensor_interface.get_smoke_value(&mq2_sensor_interface) ;
/*更新数据到队列*/
for(i = 0 ; i <= DATA_SIZE - 2 ; i++)
plot_handler.rel_data_data[i] = plot_handler.rel_data_data[i + 1];
plot_handler.rel_data_data[DATA_SIZE - 1] = smoke_value ;
So we have completed the most basic data sampling part.
3.2 Data processing part
For data processing, I defined the following functions to achieve:
void LCD_Plot_Remap(uint16_t *cur_data, uint16_t *backup_data, uint16_t cur_data_size)
cur_data represents the current real-time data packet
backup_data represents the backup data package
cur_data_size represents the length of the data packet
Real-time data packets are data packets that have not been processed, and backup data packets are processed data packets.
In this function, find the maximum and minimum values of the real-time data packet, and calculate the zoom factor:
Maximum search:
value = 0 ;
for(i = 0; i < cur_data_size; i++)
if(cur_data[i] > value)
value = cur_data[i];
max = value ;
Minimum search:
value = cur_data[0];
for(i = 0; i < cur_data_size; i++)
if(cur_data[i] < value)
value = cur_data[i];
min = value ;
Calculation of scaling factor:
max_min_diff = (float)(max - min);
scale = (float)(max_min_diff / height);
Copy the processed results to the backup data package.
The complete implementation is as follows:
/*
cur_data:当前要显示的曲线数据包
cur_data_size:当前要显示的曲线数据包的大小
*/
void LCD_Plot_Remap(uint16_t *cur_data, uint16_t *backup_data, uint16_t cur_data_size)
{
uint32_t i = 0 ;
float temp = 0;
/*数据包最大值*/
uint16_t max = 0;
/*数据包最小值*/
uint16_t min = 0;
float scale = 0.0;
uint16_t value = 0;
float max_min_diff = 0.0;
/*曲线显示的高度*/
float height = PLOT_DISPLAY_AREA_Y;
char display_rel_buf[20] = {0};
char display_max_buf[20] = {0};
char display_min_buf[20] = {0};
char display_sub_buf[20] = {0};
/*显示X坐标轴*/
for(uint8_t i = PLOT_DISPLAY_AREA_X-1 ; i < 240 ; i++)
LCD_Draw_ColorPoint(i, 239, RED);
/*显示Y坐标轴*/
for(uint8_t i = LCD_Y-PLOT_DISPLAY_AREA_Y ; i < 240 ; i++)
LCD_Draw_ColorPoint(PLOT_DISPLAY_AREA_X-1, i, RED);
value = 0 ;
for(i = 0; i < cur_data_size; i++)
if(cur_data[i] > value)
value = cur_data[i];
max = value ;
value = cur_data[0];
for(i = 0; i < cur_data_size; i++)
if(cur_data[i] < value)
value = cur_data[i];
min = value ;
sprintf(display_rel_buf,"%04d",cur_data[DATA_SIZE-1]);
sprintf(display_max_buf,"%04d",max);
sprintf(display_min_buf,"%04d",min);
sprintf(display_sub_buf,"%04d",max-min);
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+10,LCD_X,16,16,"rel:");
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+20+10,LCD_X, 16, 16, display_rel_buf);
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+50+10,LCD_X,16,16,"max:");
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+70+10,LCD_X, 16, 16, display_max_buf);
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+100+10,LCD_X,16,16,"min:");
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+120+10,LCD_X, 16, 16, display_min_buf);
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+150+10,LCD_X,16,16,"sub:");
LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+170+10,LCD_X, 16, 16, display_sub_buf);
if(min > max)
return ;
max_min_diff = (float)(max - min);
scale = (float)(max_min_diff / height);
if(cur_data_size < DATA_SIZE)
return;
for(i = 0; i < DATA_SIZE; i ++)
{
temp = cur_data[i] - min;
backup_data[i] = DATA_SIZE - (uint16_t)(temp / scale) - 1;
}
}
3.3 Data display part
This part should be the most exciting, but its implementation is the simplest, which is to connect each data in the backup data packet obtained by the data processing part with a line segment in turn. In order to make the driver faster, the following The processing is sent by register:
/*显示曲线*/
void LCD_Plot_Display(uint16_t *pData, uint32_t size, uint16_t color)
{
uint32_t i, j;
uint8_t color_L = (uint8_t) color;
uint8_t color_H = (uint8_t) (color >> 8);
if(size < DATA_SIZE) return ;
for (i = PLOT_DISPLAY_AREA_X; i < DATA_SIZE - 1; i++)
{
if (pData[i + 1] >= pData[i])
{
LCD_Address_Set(i, pData[i], i, pData[i + 1]);
LCD_DC(1);
for (j = pData[i]; j <= pData[i + 1]; j++)
{
*((uint8_t*) &hspi2.Instance->DR) = color_H;
while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);
*((uint8_t*) &hspi2.Instance->DR) = color_L;
while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);
}
}
else
{
LCD_Address_Set(i, pData[i + 1], i, pData[i]);
LCD_DC(1);
for (j = pData[i + 1]; j <= pData[i]; j++)
{
*((uint8_t*) &hspi2.Instance->DR) = color_H;
while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);
*((uint8_t*) &hspi2.Instance->DR) = color_L;
while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);
}
}
}
}
4. Real-time curve display of sensor data
The implementation logic is as follows:
while (1)
{
smoke_value = mq2_sensor_interface.get_smoke_value(&mq2_sensor_interface) ;
/*更新数据到队列*/
for(i = 0 ; i <= DATA_SIZE - 2 ; i++)
plot_handler.rel_data_data[i] = plot_handler.rel_data_data[i + 1];
plot_handler.rel_data_data[DATA_SIZE - 1] = smoke_value ;
/*先将背景刷黑*/
LCD_Plot_Display(plot_handler.old_plot_data, DATA_SIZE, BLACK);
/*传感器数据处理*/
LCD_Plot_Remap(plot_handler.rel_data_data,plot_handler.new_plot_data, DATA_SIZE);
/*传感器数据曲线显示*/
LCD_Plot_Display(plot_handler.new_plot_data, DATA_SIZE, GREEN);
/*将处理完成的备份数据区的数据拷贝到旧的备份数据区*/
memcpy(plot_handler.old_plot_data, plot_handler.new_plot_data, sizeof(plot_handler.new_plot_data));
HAL_Delay(10);
}
The code of this section has been synchronized to the code warehouse of Code Cloud. The method of obtaining it is as follows:
1. Create a new folder
2. Use git clone to get the project remotely
Project open source warehouse:
https://gitee.com/morixinguan/bear-pi
I will also upload some of the previous projects and practice routines in the near future, and share with you:
Welfare Moments for Official Account Fans
Here I have applied for the benefits for everyone. Readers of this official account can enjoy a 10% discount when purchasing the bear pie development board. Friends who need to buy the bear pie and the Tencent IoT development board can search on Taobao and tell the customer service that you are the official account: Fans of the embedded cloud IOT technology circle can enjoy a 10% discount!
Wonderful in the past
DIY a simple LCD driver framework!
Experience sharing of embedded software to solve ADC battery display problem
Importance of information about the version (take STM32 product development as an example)
TencentOS tiny hazardous gas detector product-level development and high-quality update
I feel that the article shared this time is helpful to you, [在看]
and you can click it and forward and share it, which is also my support.