前陣子公司有一個基于毒品檢測的專案需要做一個曲線顯示的功能,由于這塊是我的技能短板,因為我之前搞軟體的應用,邏輯,框架,架構設計這塊比較多,而我師弟在底層方面非常精通,所以把這一塊核心的功能交給了我師弟,讓他幫忙來實作基本的庫,然后我基于他的庫完成產品所需要的功能,
又恰好在專案之前,RT-Thread發起了一個基于RT-Thread Nano的Mini示波器DIY的活動,作為RT-Thread社區作業小組一員的我,有幸能看到這個專案從頭到尾的制作程序,也從中學習了LCD曲線資料處理和顯示的一些思想,
活動鏈接如下:
【DIY活動】一起來做一個基于RT-Thread Nano的Mini示波器吧!
完成曲線顯示大致需要以下三個步驟:
1、資料采集
2、資料處理
3、資料顯示
廢話不多說,咱們先看下顯示效果:
嚴格意義上來說,小熊派這種SPI屏其實不太適合用來刷曲線,首先解析度太低了,還有就是SPI的速率也不高,如果曲線顯示條件苛刻一點,很容易導致LCD顯示閃屏現象,體驗感非常不好,但是針對傳感器資料顯示我們還是有能力實作的,
于是,我們需要對屏驅動做一些最基本的優化:
1、優化LCD驅動
1、提升刷屏速度
由于要刷曲線,所以只能想辦法盡量提升屏的重繪速度,于是在LCD手冊里有這么一個暫存器,可以提升屏的重繪速度:
在LCD驅動初始化代碼里,這個暫存器默認配置的是60Hz,也就是0x0F這個值
/* Frame Rate Control in Normal Mode */
LCD_Write_Cmd(0xC6);
// LCD_Write_Data(0x0F); //60HZ
LCD_Write_Data(0x01); //111Hz 提升屏的重繪速度
本來設定為0x00為119Hz,但是設定完LCD就黑屏了,改為0x01就不會,目前沒找到具體原因,可能這是屏韌體的BUG,暫時將就著用吧;或者有朋友知道的,感謝在留言區分享給我,
2、改用暫存器發送
/**
* @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) {}
}
}
HAL庫的HAL_SPI_Transmit函式發送會慢一些,改用暫存器發送會更快,
2、曲線顯示邏輯
要在LCD上顯示曲線,大家可能就會有這樣的疑問:
我的資料可能上千,幾萬這樣,如何轉換成對應屏解析度的顯示?到底從哪里開始顯示?怎么顯示?
有一種比較好的思路,就是定義一個固定長度的陣列,每次往陣列尾部不斷的更新資料,然后該資料會不斷的往前推,這其實就是我們說的fifo(環形緩沖)佇列,然后定義一個新的備份緩沖區,在這個備份緩沖區里找到資料的最大值以及最小值,求出針對LCD解析度的縮放系數,根據計算結果將備份緩沖區用于LCD顯示,這就是根據實際情況進行的縮放,也叫做區域縮放,以下是這個例程的曲線資料結構:
#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 ;
由于要做到一次性更新避免閃屏,這里定義了三個緩沖區,rel_data_data用于更新實時資料,old_plot_data為舊的已經處理的顯示資料,new_plot_data為剛剛處理的顯示資料,相當于雙緩沖效果,
3、曲線顯示實作
3.1 資料采樣部分
由于剛開始顯示,曲線的資料快取是空的,所以我們要做一下初始化,保證曲線能夠直接顯示出來:
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));
接下來就是顯示邏輯上提到的,我們需要有一個環形緩沖,不斷的追加資料:
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 ;
這樣我們就完成了最基本的資料采樣部分,
3.2 資料處理部分
資料處理我定義了以下函式來實作:
void LCD_Plot_Remap(uint16_t *cur_data, uint16_t *backup_data, uint16_t cur_data_size)
cur_data表示當前的實時資料包
backup_data表示備份資料包
cur_data_size表示資料包的長度
實時資料包就是沒有經過處理的資料包,備份資料包就是經過處理的資料包,
在這個函式中主要完成了找實時資料包的最大、最小值、計算縮放系數:
最大值查找:
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 ;
縮放系數的計算:
max_min_diff = (float)(max - min);
scale = (float)(max_min_diff / height);
將處理的結果拷貝到備份資料包中,
完整實作如下:
/*
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 資料顯示部分
這部分應該是最激動人心的,但是它的實作卻是最簡單的,就是將資料處理部分得到的備份資料包里的每一個資料依次用線段連接起來即可,為了讓驅動更快一些,以下的處理采用暫存器發送:
/*顯示曲線*/
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、傳感器資料實時曲線顯示
實作邏輯如下:
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);
}
本節代碼已同步到碼云的代碼倉庫中,獲取方法如下:
1、新建一個檔案夾
2、使用git clone遠程獲取該專案
專案開源倉庫:
https://gitee.com/morixinguan/bear-pi
我還將之前做的一些專案以及練習例程在近期內全部上傳完畢,與大家一起分享交流:
公眾號粉絲福利時刻
這里我給大家申請到了福利,本公眾號讀者購買小熊派開發板可享受9折優惠,有需要購買小熊派以及騰訊物聯網開發板的朋友,淘寶搜索即可,跟客服說你是公眾號:嵌入式云IOT技術圈 的粉絲,立享9折優惠!
往期精彩
自己動手擼個簡單的LCD驅動框架吧!
嵌入式軟體解決ADC電量顯示問題經驗分享
有關版本等資訊的重要性(以STM32產品開發為例)
TencentOS tiny危險氣體探測儀產品級開發重磅高質量更新
覺得本次分享的文章對您有幫助,隨手點[在看]并轉發分享,也是對我的支持,
CSDN認證博客專家
嵌入式硬體
單片機
arm開發
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/223196.html
標籤:其他
上一篇:頸椎康復指南--桌面篇
