LCD curve display that makes sensor data more intuitive

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_Transmitfunction 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_datafor updating real-time data, which old_plot_dataare the old processed display data and new_plot_datathe 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.

Guess you like

Origin blog.csdn.net/morixinguan/article/details/109712955