【STM32F429的DSP教程】第50章 STM32F429的樣條插補實現,波形擬合絲滑順暢
完整版教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
第50章 STM32F429的樣條插補實現,波形擬合絲滑順暢
本章節講解樣條插補,主要用於波形擬合,平滑過渡。
50.1 初學者重要提示
50.2 樣條插補介紹
50.3 樣條插補實現
50.4 實驗例程說明(MDK)
50.5 實驗例程說明(IAR)
50.6 總結
50.1 初學者重要提示
DSP庫支援了樣條插補,雙線性插補和線性插補,我們這裡主要介紹樣條插補的實現。
50.2 樣條插補介紹
在數學學科數值分析中,樣條是一種特殊的函式,由多項式分段定義。樣條的英語單詞spline來源於可變形的樣條工具,那是一種在造船和工程製圖時用來畫出光滑形狀的工具。在中國大陸,早期曾經被稱做“齒函式”。後來因為工程學術語中“放樣”一詞而得名。在插值問題中,樣條插值通常比多項式插值好用。用低階的樣條插值能產生和高階的多項式插值類似的效果,並且可以避免被稱為龍格現象的數值不穩定的出現。並且低階的樣條插值還具有“保凸”的重要性質。在電腦科學的計算機輔助設計和計算機圖形學中,樣條通常是指分段定義的多項式引數曲線。由於樣條構造簡單,使用方便,擬合準確,並能近似曲線擬合和互動式曲線設計中複雜的形狀,樣條是這些領域中曲線的常用表示方法
50.3 樣條插補實現
樣條插補主要通過下面兩個函式實現。
50.3.1 函式arm_spline_init_f32
函式原型:
void arm_spline_init_f32( arm_spline_instance_f32 * S, arm_spline_type type, const float32_t * x, const float32_t * y, uint32_t n, float32_t * coeffs, float32_t * tempBuffer)
函式描述:
此函式用於樣條函式初始化。
函式引數:
- 第1個引數是arm_spline_instance_f32型別結構體變數。
- 第2個引數是樣條型別選擇:
- ARM_SPLINE_NATURAL 表自然樣條。
- ARM_SPLINE_PARABOLIC_RUNOUT 表示拋物線樣條。
- 第3個引數是原始資料x軸座標值。
- 第4個引數是原始資料y軸座標值。
- 第5個引數是原始資料個數。
- 第6個引數是插補因數快取。
- 第7個引數是臨時緩衝。
注意事項:
- x軸座標資料必須是遞增方式。
- 第6個引數插補因數快取大小問題,如果原始資料個數是n,那麼插補因數個數必須要大於等於3*(n-1)。
- 第7個引數臨時緩衝大小問題,如果原始資料個數是n,那麼臨時緩衝大小必須大於等於2*n - 1
50.3.2 函式arm_spline_f32
函式原型:
void arm_spline_f32( arm_spline_instance_f32 * S, const float32_t * xq, float32_t * pDst, uint32_t blockSize)
函式描述:
此函式用於樣條插補實現。
函式引數:
- 第1個引數是arm_spline_instance_f32型別結構體變數。
- 第2個引數是插補後的x軸座標值,需要使用者指定,注意座標值一定是遞增的。
- 第3個引數是經過插補計算後輸出的y軸數值
- 第4個引數是資料輸出個數
50.3.3 使用樣條插補函式的關鍵點
樣條插補的主要作用是使得波形更加平滑。比如一幀是128點,步大小是8個畫素,我們可以通過插補實現步長為1, 1024點的波形,本質是你的總步長大小不能變,我們這裡都是1024,這個不能變,在這個基礎上做插補,效果就出來了。
這個認識非常重要,否則無法正常使用插補演算法。
50.3.4 自然樣條插補測試
樣條測試程式碼的實現如下:
#define INPUT_TEST_LENGTH_SAMPLES 128 /* 輸入資料個數 */ #define OUT_TEST_LENGTH_SAMPLES 1024 /* 輸出資料個數 */ #define SpineTab OUT_TEST_LENGTH_SAMPLES/INPUT_TEST_LENGTH_SAMPLES /* 插補末尾的8個座標值不使用 */ float32_t xn[INPUT_TEST_LENGTH_SAMPLES]; /* 輸入資料x軸座標 */ float32_t yn[INPUT_TEST_LENGTH_SAMPLES]; /* 輸入資料y軸座標 */ float32_t coeffs[3*(INPUT_TEST_LENGTH_SAMPLES - 1)]; /* 插補係數緩衝 */ float32_t tempBuffer[2 * INPUT_TEST_LENGTH_SAMPLES - 1]; /* 插補臨時緩衝 */ float32_t xnpos[OUT_TEST_LENGTH_SAMPLES]; /* 插補計算後X軸座標值 */ float32_t ynpos[OUT_TEST_LENGTH_SAMPLES]; /* 插補計算後Y軸數值 */ /* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程式入口 * 形 參: 無 * 返 回 值: 錯誤程式碼(無需處理) ********************************************************************************************************* */ int main(void) { uint32_t i; uint32_t idx2; uint8_t ucKeyCode; arm_spline_instance_f32 S; bsp_Init(); /* 硬體初始化 */ PrintfLogo(); /* 列印例程名稱和版本等資訊 */ PrintfHelp(); /* 列印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重灌的定時器 */ /* 原始x軸數值和y軸數值 */ for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++) { xn[i] = i*SpineTab; yn[i] = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3); } /* 插補後X軸座標值,這個是需要使用者設定的 */ for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++) { xnpos[i] = i; } while (1) { bsp_Idle(); /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,自然樣條插補 */ /* 樣條初始化 */ arm_spline_init_f32(&S, ARM_SPLINE_NATURAL , xn, yn, INPUT_TEST_LENGTH_SAMPLES, coeffs, tempBuffer); /* 樣條計算 */ arm_spline_f32 (&S, xnpos, ynpos, OUT_TEST_LENGTH_SAMPLES); /* 列印輸出輸出 */ idx2 = 0; for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++) { if ((i % SpineTab) == 0) { printf("%f,%f\r\n", ynpos[i], yn[idx2++]); } else { printf("%f,\r\n", ynpos[i]); } } break; default: /* 其它的鍵值不處理 */ break; } } } }
程式碼裡面的幾個關鍵地方:
- 原始座標陣列xn和yn是128組,而我們通過插補生成的是1024組xnpos和ynpos,其中1024組的xnpos需要使用者設定初值,這點不能忽略。
- 函式arm_spline_init_f32用於樣條函式初始化,這裡特別注意,此函式主要是對原始資料的操作。自然樣條插補用的ARM_SPLINE_NATURAL。
- 函式arm_spline_f32用於樣條函式計算。
實際輸出效果如下:
50.3.5 拋物線樣條插補測試
樣條測試程式碼的實現如下:
#define INPUT_TEST_LENGTH_SAMPLES 128 /* 輸入資料個數 */ #define OUT_TEST_LENGTH_SAMPLES 1024 /* 輸出資料個數 */ #define SpineTab OUT_TEST_LENGTH_SAMPLES/INPUT_TEST_LENGTH_SAMPLES /* 插補末尾的8個座標值不使用 */ float32_t xn[INPUT_TEST_LENGTH_SAMPLES]; /* 輸入資料x軸座標 */ float32_t yn[INPUT_TEST_LENGTH_SAMPLES]; /* 輸入資料y軸座標 */ float32_t coeffs[3*(INPUT_TEST_LENGTH_SAMPLES - 1)]; /* 插補係數緩衝 */ float32_t tempBuffer[2 * INPUT_TEST_LENGTH_SAMPLES - 1]; /* 插補臨時緩衝 */ float32_t xnpos[OUT_TEST_LENGTH_SAMPLES]; /* 插補計算後X軸座標值 */ float32_t ynpos[OUT_TEST_LENGTH_SAMPLES]; /* 插補計算後Y軸數值 */ /* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程式入口 * 形 參: 無 * 返 回 值: 錯誤程式碼(無需處理) ********************************************************************************************************* */ int main(void) { uint32_t i; uint32_t idx2; uint8_t ucKeyCode; arm_spline_instance_f32 S; bsp_Init(); /* 硬體初始化 */ PrintfLogo(); /* 列印例程名稱和版本等資訊 */ PrintfHelp(); /* 列印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重灌的定時器 */ /* 原始x軸數值和y軸數值 */ for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++) { xn[i] = i*SpineTab; yn[i] = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3); } /* 插補後X軸座標值,這個是需要使用者設定的 */ for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++) { xnpos[i] = i; } while (1) { bsp_Idle(); /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K2: /* K2鍵按下,拋物線樣條插補 */ /* 樣條初始化 */ arm_spline_init_f32(&S, ARM_SPLINE_PARABOLIC_RUNOUT , xn, yn, INPUT_TEST_LENGTH_SAMPLES, coeffs, tempBuffer); /* 樣條計算 */ arm_spline_f32 (&S, xnpos, ynpos, OUT_TEST_LENGTH_SAMPLES); /* 列印輸出輸出 */ idx2 = 0; for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++) { if ((i % SpineTab) == 0) { printf("%f,%f\r\n", ynpos[i], yn[idx2++]); } else { printf("%f,\r\n", ynpos[i]); } } break; default: /* 其它的鍵值不處理 */ break; } } } }
程式碼裡面的幾個關鍵地方:
- 原始座標陣列xn和yn是128組,而我們通過插補生成的是1024組xnpos和ynpos,其中1024組的xnpos需要使用者設定初值,這點不能忽略。
- 函式arm_spline_init_f32用於樣條函式初始化,這裡特別注意,此函式主要是對原始資料的操作。拋物線樣條插補用的ARM_SPLINE_PARABOLIC_RUNOUT。
- 函式arm_spline_f32用於樣條函式計算。
實際輸出效果如下:
50.4 實驗例程說明(MDK)
配套例子:
V6-235_樣條插補,波形擬合絲滑順暢
實驗目的:
- 學習樣條插補的實現。
實驗內容:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- K1鍵按下,自然樣條插補測試。
- K2鍵按下,拋物線樣插補測試。
使用AC6注意事項
特別注意附件章節C的問題
上電後串列埠列印的資訊:
波特率 115200,資料位 8,奇偶校驗位無,停止位 1。
RTT方式列印資訊:
程式設計:
系統棧大小分配:
硬體外設初始化
硬體外設的初始化是在 bsp.c 檔案實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鐘: - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。 - 設定NVIC優先順序分組為4。 */ HAL_Init(); /* 配置系統時鐘到168MHz - 切換使用HSE。 - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。 - 預設不開啟,如果要使能此選項,務必看V5開發板使用者手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串列埠 */ bsp_InitExtIO(); /* 初始化擴充套件IO */ bsp_InitLed(); /* 初始化LED */ }
主功能:
主程式實現如下操作:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- K1鍵按下,自然樣條插補測試。
- K2鍵按下,拋物線樣插補測試。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程式入口 * 形 參: 無 * 返 回 值: 錯誤程式碼(無需處理) ********************************************************************************************************* */ int main(void) { uint32_t i; uint32_t idx2; uint8_t ucKeyCode; arm_spline_instance_f32 S; bsp_Init(); /* 硬體初始化 */ PrintfLogo(); /* 列印例程名稱和版本等資訊 */ PrintfHelp(); /* 列印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重灌的定時器 */ /* 原始x軸數值和y軸數值 */ for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++) { xn[i] = i*SpineTab; yn[i] = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3); } /* 插補後X軸座標值,這個是需要使用者設定的 */ for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++) { xnpos[i] = i; } while (1) { bsp_Idle(); /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,自然樣條插補 */ /* 樣條初始化 */ arm_spline_init_f32(&S, ARM_SPLINE_NATURAL , xn, yn, INPUT_TEST_LENGTH_SAMPLES, coeffs, tempBuffer); /* 樣條計算 */ arm_spline_f32 (&S, xnpos, ynpos, OUT_TEST_LENGTH_SAMPLES); /* 列印輸出輸出 */ idx2 = 0; for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++) { if ((i % SpineTab) == 0) { printf("%f,%f\r\n", ynpos[i], yn[idx2++]); } else { printf("%f,\r\n", ynpos[i]); } } break; case KEY_DOWN_K2: /* K2鍵按下,拋物線樣條插補 */ /* 樣條初始化 */ arm_spline_init_f32(&S, ARM_SPLINE_PARABOLIC_RUNOUT , xn, yn, INPUT_TEST_LENGTH_SAMPLES, coeffs, tempBuffer); /* 樣條計算 */ arm_spline_f32 (&S, xnpos, ynpos, OUT_TEST_LENGTH_SAMPLES); /* 列印輸出輸出 */ idx2 = 0; for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++) { if ((i % SpineTab) == 0) { printf("%f,%f\r\n", ynpos[i], yn[idx2++]); } else { printf("%f,\r\n", ynpos[i]); } } break; default: /* 其它的鍵值不處理 */ break; } } } }
50.5 實驗例程說明(IAR)
配套例子:
V6-235_樣條插補,波形擬合絲滑順暢
實驗目的:
- 學習樣條插補的實現。
實驗內容:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- K1鍵按下,自然樣條插補測試。
- K2鍵按下,拋物線樣插補測試。
上電後串列埠列印的資訊:
波特率 115200,資料位 8,奇偶校驗位無,停止位 1。
RTT方式列印資訊:
程式設計:
系統棧大小分配:
硬體外設初始化
硬體外設的初始化是在 bsp.c 檔案實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鐘: - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。 - 設定NVIC優先順序分組為4。 */ HAL_Init(); /* 配置系統時鐘到168MHz - 切換使用HSE。 - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。 - 預設不開啟,如果要使能此選項,務必看V5開發板使用者手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串列埠 */ bsp_InitLed(); /* 初始化LED */ }
主功能:
主程式實現如下操作:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- K1鍵按下,自然樣條插補測試。
- K2鍵按下,拋物線樣插補測試。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程式入口 * 形 參: 無 * 返 回 值: 錯誤程式碼(無需處理) ********************************************************************************************************* */ int main(void) { uint32_t i; uint32_t idx2; uint8_t ucKeyCode; arm_spline_instance_f32 S; bsp_Init(); /* 硬體初始化 */ PrintfLogo(); /* 列印例程名稱和版本等資訊 */ PrintfHelp(); /* 列印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重灌的定時器 */ /* 原始x軸數值和y軸數值 */ for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++) { xn[i] = i*SpineTab; yn[i] = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3); } /* 插補後X軸座標值,這個是需要使用者設定的 */ for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++) { xnpos[i] = i; } while (1) { bsp_Idle(); /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,自然樣條插補 */ /* 樣條初始化 */ arm_spline_init_f32(&S, ARM_SPLINE_NATURAL , xn, yn, INPUT_TEST_LENGTH_SAMPLES, coeffs, tempBuffer); /* 樣條計算 */ arm_spline_f32 (&S, xnpos, ynpos, OUT_TEST_LENGTH_SAMPLES); /* 列印輸出輸出 */ idx2 = 0; for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++) { if ((i % SpineTab) == 0) { printf("%f,%f\r\n", ynpos[i], yn[idx2++]); } else { printf("%f,\r\n", ynpos[i]); } } break; case KEY_DOWN_K2: /* K2鍵按下,拋物線樣條插補 */ /* 樣條初始化 */ arm_spline_init_f32(&S, ARM_SPLINE_PARABOLIC_RUNOUT , xn, yn, INPUT_TEST_LENGTH_SAMPLES, coeffs, tempBuffer); /* 樣條計算 */ arm_spline_f32 (&S, xnpos, ynpos, OUT_TEST_LENGTH_SAMPLES); /* 列印輸出輸出 */ idx2 = 0; for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++) { if ((i % SpineTab) == 0) { printf("%f,%f\r\n", ynpos[i], yn[idx2++]); } else { printf("%f,\r\n", ynpos[i]); } } break; default: /* 其它的鍵值不處理 */ break; } } } }
50.6 總結
本章節主要講解了樣條插補的實現,實際專案比較實用,有興趣可以深入原始碼瞭解。
微信公眾號:armfly_com 安富萊論壇:www.armbbs.cn 安富萊淘寶:https://armfly.taobao.com