完整版教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
第13章 DSP快速計算函數-三角函數和平方根
本期教程開始,我們將不再專門的分析DSP函數的源碼,主要是有些DSP函數的公式分析較麻煩,有興趣的同學可以自行研究,本期教程開始主要講解函數如何使用。
13.1 初學者重要提示
13.2 DSP基礎運算指令
13.3 三角函數(Cosine)
13.4 三角函數(Sine)
13.5 平方根(Sqrt)
13.6 實驗例程說明(MDK)
13.7 實驗例程說明(IAR)
13.8 總結
13.1 初學者重要提示
- 特別注意本章13.5.2小節的問題,定點數求解平方根。
- 本章13.6小節給出了Matlab2018a手動加載數據的方法。如果要看Matlab2012,參考第1版DSP教程:http://www.armbbs.cn/forum.php?mod=viewthread&tid=3886 。
13.2 DSP基礎運算指令
本章用到基礎運算指令:
- 平方根函數用到__CLZ指令,全稱是Count Leading Zero
用於求解32位數據中從bit31開始的0的個數。
- 平方根函數用到__sqrtf指令。
用於求解浮點數的平方根,用戶可以直接調用此指令,求平方根非常方便。
13.3 三角函數(Cosine)
三角函數cosine的計算是通過查表並配合直線插補實現的。
13.3.1 函數arm_cos_f32
函數原型:
float32_t arm_cos_f32(float32_t x)
函數描述:
這個函數用於求32位浮點數的cos值。
函數參數:
- 第1個參數x是弧度制,也就是cos函數的一個周期對應於弧度[ 0 2*PI)。
PI = 3.14159265358979f
- 返回值,函數返回計算結果。
Matlab計算:
下面我們先通過Matlab繪制一個周期的cos曲線。新建一個.m格式的腳本文件,並寫入如下函數:
x = 0:0.01:2*pi;
plot(x, cos(x))
運行后顯示效果如下:
點擊上面截圖中的Tools->Data statistics(工具->數據統計信息)獲取數據的分析結果,我們主要看Y軸。
最大值和最小值分別對應1和-1,這個與我們所學的理論知識是相符的。
13.3.2 函數arm_cos_q31
函數原型:
q31_t arm_cos_q31(q31_t x)
函數描述:
用於求32位定點數的cos值。
函數參數:
- 第1個參數x是弧度制,參數范圍[0 0xFFFFFFFF)(對於的浮點范圍是[0 +0.9999])相當於弧度[0 2*PI)。
- 返回值,函數返回計算結果。
13.3.3 函數arm_cos_q15
函數原型:
q15_t arm_cos_q15(q15_t x)
函數描述:
用於求16位定點數的cos值。
函數參數:
- 第1個參數x是弧度制,參數范圍[0 0xFFFF)(對於的浮點范圍是[0 +0.9999])相當於弧度[0 2*PI)。
- 返回值,函數返回計算結果。
13.3.4 使用舉例
程序設計:
/* ********************************************************************************************************* * 函 數 名: DSP_Cosine * 功能說明: 求cos函數 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void DSP_Cosine(void) { uint16_t i; /***************************cos函數*****************************************/ for(i = 0; i < 256; i++) { /* 參數的輸入范圍是[0 2*pi) */ printf("%f\r\n", arm_cos_f32(i * PI / 128)); } printf("***************************************************************\r\n"); for(i = 0; i < 256; i++) { /* 這里是0 到 0xFFFF對應[0 2*pi) */ printf("%d\r\n", arm_cos_q15(i*128)); } printf("***************************************************************\r\n"); for(i = 0; i < 256; i++) { /* 這里是0 到 0xFFFFFFFF對應[0 2*pi) */ printf("%d\r\n", arm_cos_q31(i*8388608)); } printf("***************************************************************\r\n"); }
實驗現象:
上面是部分計算結果截圖。
13.4 三角函數(Sine)
三角函數sine的計算是通過查表並配合直線插補實現的。具體的實現方法大家可以查閱相關資料進行了解。
13.4.1 函數arm_sin_f32
函數原型:
float32_t arm_sin_f32(float32_t x)
函數描述:
這個函數用於求32位浮點數的sin值。
函數參數:
- 第1個參數x是弧度制,也就是sin函數的一個周期對應於弧度[ 0 2*PI)。
PI = 3.14159265358979f
- 返回值,函數返回計算結果。
Matlab計算:
下面我們先通過Matlab繪制一個周期的sin曲線。新建一個.m格式的腳本文件,並寫入如下函數:
x = 0:0.01:2*pi;
plot(x, sine(x))
運行后顯示效果如下:
點擊上面截圖中的Tools->Data statistics(工具->數據統計信息)獲取數據的分析結果,我們主要看Y軸。
最大值和最小值分別對應1和-1,這個與我們所學的理論知識是相符的。
13.4.2 函數arm_sin_q31
函數原型:
q31_t arm_sin_q31(q31_t x)
函數描述:
用於求32位定點數的sin值。
函數參數:
- 第1個參數x是弧度制,參數范圍[0 0xFFFFFFFF)(對於的浮點范圍是[0 +0.9999])相當於弧度[0 2*PI)。
- 返回值,函數返回計算結果。
13.4.3 函數arm_sin_q15
函數原型:
q15_t arm_cos_q15(q15_t x)
函數描述:
用於求16位定點數的sin值。
函數參數:
- 第1個參數x是弧度制,參數范圍[0 0xFFFF)(對於的浮點范圍是[0 +0.9999])相當於弧度[0 2*PI)。
- 返回值,函數返回計算結果
13.4.4 使用舉例
程序設計:
/* ********************************************************************************************************* * 函 數 名: DSP_Sine * 功能說明: 求sine函數 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void DSP_Sine(void) { uint16_t i; /***************************sin函數*****************************************/ for(i = 0; i < 256; i++) { /* 參數的輸入范圍是[0 2*pi) */ printf("%f\r\n", arm_sin_f32(i * PI / 128)); } printf("***************************************************************\r\n"); for(i = 0; i < 256; i++) { /* 這里是0 - 0xFFFF 對應 [0 2*pi) */ printf("%d\r\n", arm_sin_q15(i*128)); } printf("***************************************************************\r\n"); for(i = 0; i < 256; i++) { /* 這里是0 - 0xFFFFFFFF 對應 [0 2*pi) */ printf("%d\r\n", arm_sin_q31(i*0x800000)); } printf("***************************************************************\r\n"); }
實驗現象:
上面是部分計算結果截圖。
13.5 平方根(Sqrt)
浮點數的平方根計算只需調用一條浮點指令即可,而定點數的計算要稍顯麻煩。
13.5.1 函數arm_sqrt_f32
函數原型:
__STATIC_FORCEINLINE arm_status arm_sqrt_f32(
float32_t in,
float32_t * pOut)
函數描述:
這個函數用於求32位定點數的平方根,對於帶FPU的處理器來說,浮點數的平方根求解很簡單,只需調用指令__sqrtf,僅需要14個時鍾周期就可以完成。
函數形參:
- 第1個參數是源數據地址。
- 第2個參數求平方根后的數據地址。
13.5.2 函數arm_sqrt_q31
函數原型:
arm_status arm_sqrt_q31(
q31_t in,
q31_t * pOut)
函數描述:
這個函數用於求32位定點數的平方根。
函數參數:
- 第1個參數是源數據地址,參數范圍0x00000000 到 0x7FFFFFFF。
- 第2個參數是求平方根后的數據地址。
- 返回值,返回ARM_MATH_SUCCESS表示計算成功,返回ARM_MATH_ARGUMENT_ERROR表示計算出錯。
注意事項:
這里in的輸入范圍是0x00000000 到 0x7FFFFFFF,轉化成浮點數范圍就是[0 +1)。在使用這個函數的時候有一點要特別的注意,比如我們要求1000的平方根,而獲得結果是1465429,這是為什么呢,分析如下:
定點數1000 = 浮點數 1000 /(2^31) = 4.6566e-07 (用Q31表示)。
對4.6566e-07求平方根可得 6.8239e-04。
定點數1465429 = 浮點數 1465429/(2^31) = 6.8239e-04。
簡單的總結下上面的意思就是說,求定點數1000的平方根,實際是求浮點數4.6566e-07 (用Q31表示)的平方根
13.5.3 函數arm_sqrt_q15
函數原型:
arm_status arm_sqrt_q15(
q15_t in,
q15_t * pOut)
函數描述:
這個函數用於求16點數的平方根
函數參數:
- 第1個參數是源數據地址,參數范圍0x0000 到 0x7FFF(轉化成浮點數范圍就是[0 +1))。
- 第2個參數是求平方根后的數據地址。
- 返回值,返回ARM_MATH_SUCCESS表示計算成功,返回ARM_MATH_ARGUMENT_ERROR表示計算出錯
13.5.4 使用舉例
程序設計:
/* ********************************************************************************************************* * 函 數 名: DSP_Shift * 功能說明: 移位 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void DSP_Shift(void) { q31_t pSrcA1 = 0x88886666; q31_t pDst1; q15_t pSrcA2 = 0x8866; q15_t pDst2; q7_t pSrcA3 = 0x86; q7_t pDst3; /*求移位*********************************/ arm_shift_q31(&pSrcA1, 3, &pDst1, 1); printf("arm_shift_q31 = %8x\r\n", pDst1); arm_shift_q15(&pSrcA2, -3, &pDst2, 1); printf("arm_shift_q15 = %4x\r\n", pDst2); arm_shift_q7(&pSrcA3, 3, &pDst3, 1); printf("arm_shift_q7 = %2x\r\n", pDst3); printf("***********************************\r\n"); }
實驗現象:
13.6 Matlab驗證(手動加載數據到Matlab的方法)
這里我們采樣了cos曲線一個周期中的256個點。為了驗證結果是否正確,我們可以將這些數據保存到txt文件中,復制這256個數據即可,然后保存並關閉文件。通過matlab加載這個txt文件,加載方法如下:
加載保存好數據的txt文件(特別注意輸出類型選擇列向量):
然后點擊右上角那個綠色對勾,會提示變量已經導入:
然后再看工作區(Workspace)就能看到添加的數組變量了:
現在我們通過matlab中的plot功能繪制下這些數據,在的VarName1的地方右擊鼠標,選擇plot
繪制后的結果如下:
從波形上看基本是一個周期的cos函數曲線。
13.7 實驗例程說明(MDK)
配套例子:
V5-208_DSP快速運算(三角函數和平方根)
實驗目的:
- 學習DSP快速運算(三角函數和平方根)
實驗內容:
- 按下按鍵K1, DSP求Cosine。
- 按下按鍵K2, DSP求Sine。
- 按下按鍵K3, DSP求平方根。
使用AC6注意事項
特別注意附件章節C的問題
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1。
詳見本章的3.4 4.4,5.4小節。
程序設計:
系統棧大小分配:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F407 HAL 庫初始化,此時系統用的還是F407自帶的16MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為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 */ bsp_InitESP8266(); /* 配置ESP8266模塊相關的資源 */ }
主功能:
主程序實現如下操作:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- 按下按鍵K1, DSP求Cosine。
- 按下按鍵K2, DSP求Sine。
- 按下按鍵K3, DSP求平方根。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程信息到串口1 */ PrintfHelp(); /* 打印操作提示信息 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重裝的定時器 */ /* 進入主程序循環體 */ 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鍵按下,求Cosine */ DSP_Cosine(); break; case KEY_DOWN_K2: /* K2鍵按下, 求Sine */ DSP_Sine(); break; case KEY_DOWN_K3: /* K3鍵按下,求平方根 */ DSP_Sqrt(); break; default: /* 其他的鍵值不處理 */ break; } } } }
13.8 實驗例程說明(IAR)
配套例子:
V5-208_DSP快速運算(三角函數和平方根)
實驗目的:
- 學習DSP快速運算(三角函數和平方根)
實驗內容:
- 按下按鍵K1, DSP求Cosine。
- 按下按鍵K2, DSP求Sine。
- 按下按鍵K3, DSP求平方根。
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1。
詳見本章的3.4 4.4,5.4小節。
程序設計:
系統棧大小分配:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F407 HAL 庫初始化,此時系統用的還是F407自帶的16MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為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, DSP求Cosine。
- 按下按鍵K2, DSP求Sine。
- 按下按鍵K3, DSP求平方根。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程信息到串口1 */ PrintfHelp(); /* 打印操作提示信息 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重裝的定時器 */ /* 進入主程序循環體 */ 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鍵按下,求Cosine */ DSP_Cosine(); break; case KEY_DOWN_K2: /* K2鍵按下, 求Sine */ DSP_Sine(); break; case KEY_DOWN_K3: /* K3鍵按下,求平方根 */ DSP_Sqrt(); break; default: /* 其他的鍵值不處理 */ break; } } } }
13.9 總結
本期教程就跟大家講這么多,有興趣的可以深入研究這些函數源碼的實現。