今学期最初の研修では、ワンチップマイコンを使って1KHzから40KHzまでの一段階可変周波数の正弦波出力を実現する必要があり、最初はこの皿を使って出力波形テーブルの周波数を変更したり、タイミングを修正したりしていました。タイマのプリスケーラ係数と自動リロード値を変更することにより、デバイス+ DDSの2 つの方法がありますが、その効果は理想的ではなく、波形が見栄えが悪いだけでなく、37KHz の周波数も正確ではありません。情報を参考にし、友人と話し合った結果、 DMA ダブルバッファリング + DDSを使用して波形発生器を実現することにしました。その効果は、波形の見栄えが良いだけでなく、周波数も比較的正確です。
stm32 の DMA ダブルバッファリングの実装については、インターネット上にあまり情報がありません。ここでは、私が独自に実装した 2 つの方法を書き留めておきます。一方で、将来使用するときにすぐに見つけられるようにする一方で、ダブルバッファリングを使用したいので、お役に立てれば幸いです。
(ハン兄弟、郭子、その他の友人たちの熱心な援助に感謝します(*^▽^*))
2 つの方法は次のとおりです。
1. DMA ハーフフル割り込みおよびフルフル割り込みの実装
@ヨーグルト
1 つ目は構成であり、DAC の DMA を構成します。
ここでは DMA を TIM4 に結び付けていますが、必要に応じて変更できます。
ここでは、より高い周波数を出力するために、クロック周波数を最大まで引き上げます。
追伸:
ここで、割り込みに関しては、変な問題を避けるために、DAC の DMA 割り込み優先順位を少し高く設定するのが最善であることを説明します。
次はコードです。コードは比較的単純で、主に dma 割り込みの HAL ライブラリに付属する 2 つのコールバック関数を使用しています。
void HAL_DAC_ConvHalfCPltCallbackCh1(DAC_HandleTypeDef *hdac); //ハーフフル割り込みコールバック
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac); //フル完全割り込みコールバック
名前が示すように、ハーフフルでは、ステートメント HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, 4000, DAC_ALIGN_12B_R); を使用すると、DMA がデータを dacBuf の半分の長さである 4000/2=2000 に転送します。の場合はハーフフル割り込みコールバックに入り、送信データが4000番目に達すると、つまりすべての送信が完了するとフルフル割り込みコールバックに入ります。
2 つのコールバック関数は弱い関数なので、自分で書き直してから、DDS などの独自のコードを書き込む必要があります。これら 2 つのコールバック関数は HAL_DAC_Start_DMA 関数内にあり、自分で見つけることができます。
ここで注意してください: HAL_DAC_Start_DMA が呼び出されると、DMA のハーフフル、フルフル、およびエラー割り込みが自動的に有効になります。割り込みせずに DMA を有効にする場合は、次のように手動で停止することをお勧めします。以下の .c ファイルをコメントアウトします (このように直接コメントするとどのような問題が発生するか作者は知りません。知っている人がいたら作者に連絡して修正してください)。
おおよそのコード フローは次のとおりです。
1. 主な機能
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_DMA_Init();
MX_DAC_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_TIM6_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
// delay_init(168);
HAL_TIM_Base_Start(&htim4);
HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, SINN, DAC_ALIGN_12B_R); //¿ªÆôdac1£¬Êä³öÕýÏÒ²¨
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
そうです、
main 関数に必要な文は HAL_TIM_Base_Start(&htim4); HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, SINN, DAC_ALIGN_12B_R); の 2 つの文だけです。
ここで、DMA の初期化は DAC の初期化の前に配置する必要があることに注意してください。そうしないと問題が発生します。
2. 独自のハーフフル割り込みおよびフルフル割り込み
#define SINN 4000
double scrb=0.5; //幅度参数
uint16_t ftw=10; //步进
uint16_t oudress; //地址
uint16_t sinI; //输出正弦波数组索引
void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac) //半满中断回调
{
u16 i=0;
for(i=0; i<2000; i++) //在半满回调中更改前半个周期的数据
{
dacBuf[sinI] = (sinBuf[oudress]-1862)*scrb+2062; //循环里边的是DDS
sinI++; //大家可以自己去了解DDS原理及应用
oudress+=ftw;
if(oudress>4000 || oudress==4000) oudress%=4000;
if(sinI==4000) sinI=0;
}
}
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac) //全满中断回调
{
u16 i=0;
for(i=0; i<2000; i++) //在全满回调中更改后半个周期的数据
{
dacBuf[sinI] = (sinBuf[oudress]-1862)*scrb+2062;
sinI++;
oudress+=ftw;
if(oudress>4000 || oudress==4000) oudress%=4000;
if(sinI==4000) sinI=0;
}
}
周波数を変更するには、一定の時間が経過した後のタイマー割り込みのステップ ftw の値を変更することで、周波数変換を変更します。
上記は、DMA ダブル バッファリングを実現する最初の方法であり、比較的簡単です。
2. 比較的本格的なダブルバッファリング
@果子
この方法でのキューブの構成は最初の方法と同じで、DMA とタイマーを構成するだけです。
ここでは原則についてはあまり触れず、実装方法についてのみ説明しますが、著者がよく説明していると思われる記事へのリンクを以下に貼りますので、必要な場合は読んでください。
1. 主な機能
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_DMA_Init();
MX_DAC_Init();
MX_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
__HAL_DAC_ENABLE(&hdac, DAC_CHANNEL_1); //提前使能DAC外设
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn,2,0);
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
MY_Buffer_Init(); //初始化两个缓冲区的数据
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_2,(uint32_t *) MY_M0_Buffer,50,DAC_ALIGN_12B_R); //开启DAC
HAL_DMAEx_MultiBufferStart_IT(&hdma_dac2, (u32)&DAC1->DHR12R1, (u32)Pm0, (u32)Pm1, 100); //开启DMA双缓冲模式中断
HAL_TIM_Base_Start(&htim2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
LED1_T;
HAL_Delay(1000);
LED0_T;
}
/* USER CODE END 3 */
}
2. いくつかの定義
/* USER CODE BEGIN 0 */
void MY_Buffer_Init(void); //初始化两个缓冲区的数据
volatile float MY_U=3.3; //用volatile是防止寄存器优化使程序出问题
uint16_t outadress=0; //地址
uint16_t sinBuf[4000]={2047,2050,2053,2057,2060,2063,.....}; //随便一个正弦波表,这里太长就不放了
uint16_t MY_M0_Buffer[50]; //DMA第一个缓冲区
uint16_t MY_M1_Buffer[50]; //DMA第二个缓冲区
uint16_t *Pm0=MY_M0_Buffer, *Pm1=MY_M1_Buffer;
uint16_t MY_F=40; //步进
/* USER CODE END 0 */
3. 関連機能
/* USER CODE BEGIN 4 */
void dma_m0_rxcplt_callback(DMA_HandleTypeDef *hdma) //自己写的第一个回调函数,相当于半满
{
u8 i=0;
for(i=0;i<50;i++) //DDS
{
MY_M0_Buffer[i] = sinBuf[outadress];
outadress = (outadress + MY_F)%4000;
}
}
void dma_m1_rxcplt_callback(DMA_HandleTypeDef *hdma) //自己写的第二个回调函数,相当于全满
{
u8 i=0;
for(i=0;i<50;i++)
{
MY_M1_Buffer[i] = sinBuf[outadress];
outadress = (outadress + MY_F)%4000;
}
}
void MY_Buffer_Init(void) //初始化缓冲区数据
{
u8 i=0;
for(i=0;i<50;i++)
{
MY_M0_Buffer[i] = sinBuf[outadress];
outadress = (outadress + MY_F)%4000;
}
for(i=0;i<50;i++)
{
MY_M1_Buffer[i] = sinBuf[outadress];
outadress = (outadress + MY_F)%4000;
}
}
/* USER CODE END 4 */
4.ここで重要な点が発生します。次の点に注意してください。hal ライブラリの一部の内部構成を変更する必要があります。
(1) まず、以下のファイルにある HAL_DMAEx_MultiBufferStart_IT の関数定義を入力します。
(2) 独自のコールバック関数定義を追加する
(3) これら 2 つの文を適切な場所に追加します
(4) まず無効にする必要があります
全体としては以下の通り
/**
* @brief Starts the multi_buffer DMA Transfer with interrupt enabled.
* @param hdma pointer to a DMA_HandleTypeDef structure that contains
* the configuration information for the specified DMA Stream.
* @param SrcAddress The source memory Buffer address
* @param DstAddress The destination memory Buffer address
* @param SecondMemAddress The second memory Buffer address in case of multi buffer Transfer
* @param DataLength The length of data to be transferred from source to destination
* @retval HAL status
*/
extern void dma_m0_rxcplt_callback(DMA_HandleTypeDef *hdma);
extern void dma_m1_rxcplt_callback(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMAEx_MultiBufferStart_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t SecondMemAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;
/* Check the parameters */
assert_param(IS_DMA_BUFFER_SIZE(DataLength));
/* Memory-to-memory transfer not supported in double buffering mode */
if (hdma->Init.Direction == DMA_MEMORY_TO_MEMORY)
{
hdma->ErrorCode = HAL_DMA_ERROR_NOT_SUPPORTED;
return HAL_ERROR;
}
// /*Current memory buffer used is Memory 1 callback*/
hdma->XferCpltCallback=dma_m0_rxcplt_callback; //第一个缓冲区填满后调用
// /*Current memory buffer used is Memory 0 callback*/
hdma->XferM1CpltCallback=dma_m1_rxcplt_callback; //第二个缓冲区填满后调用
/* Check callback functions */
if ((NULL == hdma->XferCpltCallback) || (NULL == hdma->XferM1CpltCallback) || (NULL == hdma->XferErrorCallback))
{
hdma->ErrorCode = HAL_DMA_ERROR_PARAM;
return HAL_ERROR;
}
/* Process locked */
__HAL_LOCK(hdma);
/*Enable the peripheral*/
__HAL_DMA_DISABLE(hdma); //先失能DMA后边的设置才会生效
if(HAL_DMA_STATE_READY == hdma->State)
{
/* Change DMA peripheral state */
hdma->State = HAL_DMA_STATE_BUSY;
/* Initialize the error code */
hdma->ErrorCode = HAL_DMA_ERROR_NONE;
/* Enable the Double buffer mode */
hdma->Instance->CR |= (uint32_t)DMA_SxCR_DBM;
/* Configure DMA Stream destination address */
hdma->Instance->M1AR = SecondMemAddress;
/* Configure the source, destination address and the data length */
DMA_MultiBufferSetConfig(hdma, SrcAddress, DstAddress, DataLength);
/* Clear all flags */
__HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));
__HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_HT_FLAG_INDEX(hdma));
__HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_TE_FLAG_INDEX(hdma));
__HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_DME_FLAG_INDEX(hdma));
__HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_FE_FLAG_INDEX(hdma));
/* Enable Common interrupts*/
hdma->Instance->CR |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME;
hdma->Instance->FCR |= DMA_IT_FE;
if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
{
hdma->Instance->CR |= DMA_IT_HT;
}
/* Enable the peripheral */
__HAL_DMA_ENABLE(hdma);
}
else
{
/* Process unlocked */
__HAL_UNLOCK(hdma);
/* Return error status */
status = HAL_BUSY;
}
return status;
}
3. まとめ
以上が私が実践した2つのダブルバッファリング実装方法ですが、ソースプログラムが必要な方はプライベートメッセージを送信していただけます。
そして、これが DDS 原則のブログのプッシュです。
https://www.cnblogs.com/puyu9495/archive/2022/02/19/15914090.html
誰もがそれを拾う必要があります。さあさあ!