3.3 V ノイズジェネレータ (12)

STM32 の SPI モジュールには CRC 計算機能が組み込まれていて、8 ビットおよび 16 ビットの SPI モードに対応して、それぞれ 8 ビット、16 ビットの CRC を自動計算することができます。
外付けの 16 ビット・シリアル入力、ディジタル・オーディオ用 DAC のインターフェースとして SPI を使っていますから、オーディオ DAC からホワイトノイズを出力させるためにこの機能を使うと便利です。
CRC 計算の生成多項式は固定値ではなく、 (8/16 ビットの制限はありますが) 自由に選ぶことができます。
生成多項式のビットパターンを設定するレジスタの初期値は 0x07 となっており、これは 8 ビットモードでの CRC-8 (-ATM) の生成多項式

   g(x) = x8 + x2 + x1 + 1

に対応するものです。 それ以外の生成多項式を使う場合には、そのビットパターンをレジスタに設定する必要があります。
ここでは、16 次の生成多項式の中から、項数が 9 の、

   g(x) = x16 + x15 + x13 + x10 + x9 + x7 + x6 + x2 + 1

を適当に選びました。 この生成多項式で生成される m-系列の統計的性質については調べてありません。
レジスタに設定する値としては「0xA6C5」になります。
SPI モジュールの CRC 計算回路は送信側と受信側とが独立しています。
しかし、送信側の CRC 計算回路は、m-系列発生の目的には使えません。
CRC の計算は、LFSR (Linear Feedback Shift Register) のフィードバック経路に exclusive-OR で入力データを加えることで行っています。
この CRC 計算回路を m-系列発生器として使うには、

  • シフトレジスタにオールゼロではない初期値を設定しておく
  • 入力データを常に「0」(あるいは常に「1」) に保って計算させる
  • 求まった CRC レジスタの値を出力

とします。
送信側 CRC の計算は、当然、SPI 送信データについて行われますから、CRC 計算入力をオールゼロにするためには、SPI のデータレジスタ (SPIx->DR) には常に 0x0000 を書き込まなければならず、これでは、DAC からは直流しか出てきません。
また送信 CRC レジスタ (SPIx->TXCRCR) の値を読み取って SPI データレジスタに書き込むと、それまで計算した CRC の値を CRC 計算回路に入力することになり、結果の CRC の値はゼロになってしまい、期待したような動作にはなりません。
DAC 出力のために SPI 出力 (MOSI) を利用していますが、SPI 入力 (MISO) は未使用なので、空いている受信側の CRC 計算回路を m-系列発生に使うことができます。
SPI の入力ピン (MISO) には外部に何もつながず、プルアップ入力あるいはプルダウン入力に設定すれば、常に「1」または「0」を入力し続けることができます。
1 ワード分の送受信のあと、受信データ 8 ビット分あるいは 16 ビット分に対する CRC の計算結果は受信 CRC レジスタ (SPIx->RXCRCR) に現れますから、これをデータレジスタ (SPIx->DR) に書き込んで次の送信データとすることを繰り返せば m-系列データを DAC に送ることができます。
この SPIx->RXCRCR から SPIx->DR への転送は、DMA を使えば CPU の介在なしに実行させることができます。
下にプログラムの例を示します。 STMicro の標準ライブラリを使う形に書いたので、少し長くなっています。
システムクロックが 72 MHz であることを仮定しており、サンプリング周波数は 48 kHz です。 簡単のため、DAC からホワイトノイズが出るだけで、ボード上の LED すら点灯しないプログラムとなっています。
DAC との接続は、これまでのプログラムと変わらず、

STM 側ピン DAC 側ピン
PB1 LRCK
PB13 BCLK
PB15 SDAT

となっています。
L 側は CRC 計算モジュールによる m-系列を出力し、R 側は SPI CRC 計算回路による m-系列を出力しています。 まじめにステレオ出力データを送っているので、BU9480F 以外の DAC でも音は出ると思います。
最初のほうにある「USE_DMA」というシンボルを定義してあると SPI CRC を DMA を使ってソフトウェアの介在なしに出力し、シンボルが未定義なら普通の割り込みルーチンのなかで出力するようになっています。
現在、CQ-STARM 基板で SWD (Serial Wire Debug) 信号の読み書きの実験を SPI2 を使って行っている関係上、MOSI と MISO が接続された状態になっています。
そのため、MISO のプルアップだけでは連続「1」入力とはならないので、SPI2 の初期化後に 1 ワードだけ 0xFFFF の送信を行い、CRC レジスタをオールゼロ以外の状態とした上で、MISO 入力を「アナログ入力モード」に設定しています。
「アナログ入力モード」では、ディジタル入力バッファであるシュミットトリガの動作が停止されて、ポートに加わる電圧レベルによらずに、ディジタル入力としては常に「0」が読み出される状態となります。
SPI データレジスタへのデータ転送は DMA あるいは割り込みルーチン内で行われるので、メインプログラムとしては、初期化が終了した後は、中身が空っぽの無限ループとなっています。

/*
 *  STM32F103xx white noise generation
 *  by m-sequence using CRC & SPI module
 *  for external serial DAC
 */

#include "stm32f10x_conf.h"

#define USE_DMA

//
// 16-bit generator polynomial
//
// g(x) = x^16 + x^15 + x^13 + x^10 + x^9 + x^7 + x^6 + x^2 + 1
//      =  {16, 15, 13, 10, 9, 7, 6, 2, 1}
//      =  0xA6C5   
 
#define GX_INIT (0xA6C5)

// TIM3 clock = 72 MHz / 2 * 2 = 72 MHz (APB1 domain)
#define TIM_CLK (72000000UL)

// sampling frequency
#define FS_kHz  (48000UL)

// divide count for 48 kHz
#define DIV48kHz ((uint16_t)((double)TIM_CLK/FS_kHz+0.5))

// Timer3 IRQ handler
void TIM3_IRQHandler( void )
{
  if (TIM_GetFlagStatus(TIM3,  TIM_FLAG_Update)) {  // TIM3 UIF set?
    SPI_I2S_SendData(SPI2, (uint16_t)CRC_CalcCRC(0));
    TIM_ClearFlag(TIM3, TIM_FLAG_Update);
#if !defined(USE_DMA)    
  } else if (TIM_GetFlagStatus(TIM3, TIM_FLAG_CC4)) {  // TIM3 CC4IF set?
    SPI_I2S_SendData (SPI2, SPI_GetCRC(SPI2, SPI_CRC_Rx)); // output random
    TIM_ClearFlag(TIM3, TIM_FLAG_CC4);
#endif    
  } // if (TIM_GetFlagStatus(TIM3,  ...
} // void TIM3_IRQ_Handler()

void GPIO_configuration( void )
{
  GPIO_InitTypeDef GPIO_InitStruct;
  
// enable clock for GPIO B

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
// TIM3_CH4 (PB1) for DAC LRCK
  GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_1;
  GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(GPIOB, &GPIO_InitStruct);

// SPI2_SCK (PB13) is push-pull output for DAC BCLK
  GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_13;
  GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(GPIOB, &GPIO_InitStruct);

// SPI2_MOSI (PB15) is push-pull output for DAC SDAT
  GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_15;
  GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(GPIOB, &GPIO_InitStruct);

// SPI2_MISO (PB14) is pullup input
  GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_14;
  GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;
  GPIO_Init(GPIOB, &GPIO_InitStruct);
} // void GPIO_configuration()

void timer_configuration( void )
{
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
  TIM_OCInitTypeDef       TIM_OCInitStruct;
  
// Timer 3 setup ( fs and DAC LRCK generation )

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  
  TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
  TIM_TimeBaseInitStruct.TIM_CounterMode   = TIM_CounterMode_Down; 
  TIM_TimeBaseInitStruct.TIM_Period        = DIV48kHz - 1; // 72 MHz / 1500 = 48 kHz
  TIM_TimeBaseInitStruct.TIM_Prescaler     =  0;  // prescale 1/1
  
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
  
  TIM_OCInitStruct.TIM_OCMode      = TIM_OCMode_PWM1;
  TIM_OCInitStruct.TIM_OCPolarity  = TIM_OCPolarity_Low;
  TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStruct.TIM_Pulse       = (DIV48kHz >> 1);
  
  TIM_OC4Init(TIM3, &TIM_OCInitStruct);
  
  TIM_CCxCmd(TIM3, TIM_Channel_4, TIM_CCx_Enable);
  
  TIM_ARRPreloadConfig(TIM3, ENABLE); // auto preload enable
  
  TIM_ITConfig(TIM3, 
               TIM_IT_Update // UIF
#if !defined(USE_DMA)               
               | TIM_IT_CC4  // CC4IF
#endif
               , ENABLE);
               
  TIM_DMACmd(TIM3, TIM_DMA_CC4, ENABLE); 
           
  TIM_Cmd(TIM3, ENABLE); // enable timer 3
} // void timer_configuration()

void NVIC_configuration()
{
  NVIC_InitTypeDef NVIC_InitStruct;
  
// room for DFU firmware
  NVIC_SetVectorTable(0x3000, 0); 
  
  NVIC_InitStruct.NVIC_IRQChannel    = TIM3_IRQn;
  NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority  = 15; 
  NVIC_InitStruct.NVIC_IRQChannelSubPriority         = 15;
  
  NVIC_Init(&NVIC_InitStruct);
} // void NVIC_configuration()

void CRC_configuration( void )
{
// enable clock for CRC module
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);
  CRC_ResetDR(); // preset CRC data register
} // void CRC_configuration()

void SPI_configuration()
{
  SPI_InitTypeDef   SPI_InitStruct;
  GPIO_InitTypeDef  GPIO_InitStruct;
  
// Enable SPI2 clock
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); 
    
// SPI clock = 36 MHz / 16 = 2.25 MHz    
  SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
  SPI_InitStruct.SPI_CPHA      = SPI_CPHA_1Edge;
  SPI_InitStruct.SPI_CPOL      = SPI_CPOL_Low;
  SPI_InitStruct.SPI_DataSize  = SPI_DataSize_16b;
  SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  SPI_InitStruct.SPI_FirstBit  = SPI_FirstBit_MSB;
  SPI_InitStruct.SPI_Mode      = SPI_Mode_Master;
  SPI_InitStruct.SPI_NSS       = SPI_NSS_Soft;
  SPI_InitStruct.SPI_CRCPolynomial = GX_INIT;
  
  SPI_Init(SPI2, &SPI_InitStruct);
  SPI_NSSInternalSoftwareConfig(SPI2, SPI_NSSInternalSoft_Set);
  SPI_CalculateCRC(SPI2, ENABLE); 
 
  SPI_Cmd(SPI2,  ENABLE);   
  
  SPI_I2S_SendData (SPI2, 0xFFFF); // preset Rx CRC
// wait until xmit completed
  do {} 
    while (RESET == SPI_I2S_GetFlagStatus (SPI2, SPI_I2S_FLAG_TXE)); 
  
// SPI2_MISO (PB14) is analog input (read as digital '0')
  GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_14;
  GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AIN;
  GPIO_Init(GPIOB, &GPIO_InitStruct);
} // void SPI_configuration()

void DMA_configuration( void )
{
  DMA_InitTypeDef DMA_InitStruct;

// enable clock for DMA1 module
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  
  DMA_StructInit(&DMA_InitStruct);
  DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->RXCRCR);
  DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t)&(SPI2->DR);
  DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
  DMA_InitStruct.DMA_Mode               = DMA_Mode_Circular;
  DMA_InitStruct.DMA_BufferSize         = DMA_PeripheralDataSize_HalfWord;
  DMA_Init(DMA1_Channel3, &DMA_InitStruct);
  
  DMA_Cmd(DMA1_Channel3, ENABLE);
} // void DMA_configuration()

int main(void)
{
// STM32 initailization
  SystemInit();
  NVIC_configuration();
  GPIO_configuration();
  SPI_configuration();
  CRC_configuration();
#if defined(USE_DMA)    
  DMA_configuration();
#endif
  timer_configuration();
  while (1) {} // infinite loop
} // void main()