2020年08月29日

外部高速ADC接続方法 その2

以前に外部高速ADC接続方法検討として、MISOがパラレルになっている外付けADCをどうSTM32マイコンと接続するか検討しました。アナログデバイセズのサイトでも接続方法について解説がありました。AnalogDevicesのLTC2358、LTC2458といったADCではMISOがパラレルになっており、アナログデバイセズのサイトでも接続方法の中でもSolution4を適用できます。

以前の外部高速ADC接続方法検討では複数SPIを同時に利用するためにDMA転送は必須と説明しましたが、厳密にいえば必須ではありません。HALライブラリを使用した場合には必須ですが、アナログデバイセズのサイトの例をみて頂ければ分かる通り、HALライブラリを使用せずに直接SPIのレジスタ、バッファにアクセスすることで同時に複数のSPIをDMA転送を使用せずに通信することが可能です。

アナログデバイセズのサイトの各SolutionではSTM32F4を使用してコード例を記載していますが、STM32H7等ではレジスタ等が異なるため、そのまま適用できません。今回はSTM32H7等でHALライブラリを使用せずに直接SPIのレジスタ、バッファにアクセスする方法について紹介します。

SPI1をFull-Duplex Master、SPI2をRX-only Slaveとして、SPI1とSPI2を同時に使用する場合を紹介します。ADCのSDOラインが2つ以上ある場合はRX-only Slaveを増やして対応することが可能です。

■SPI初期化
初期化ではHALライブラリをそのまま使用します。設定のポイントとして、SPI1をマスタ、SPI2をスレーブとして設定する他にNSSを共にソフトウェアに設定する点です。

void MX_SPI1_Init(void)
{
 hspi1.Instance = SPI1;
 hspi1.Init.Mode = SPI_MODE_MASTER;
 hspi1.Init.Direction = SPI_DIRECTION_2LINES;
 hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
 hspi1.Init.NSS = SPI_NSS_SOFT;
 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
 hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
 hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
 hspi1.Init.CRCPolynomial = 0x0;
 hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
 hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
 hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
 hspi1.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
 hspi1.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
 hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
 hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
 hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
 hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
 hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
  Error_Handler();
 }
}

void MX_SPI2_Init(void)
{
 hspi2.Instance = SPI2;
 hspi2.Init.Mode = SPI_MODE_SLAVE;
 hspi2.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
 hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
 hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
 hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
 hspi2.Init.NSS = SPI_NSS_SOFT;
 hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
 hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
 hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
 hspi2.Init.CRCPolynomial = 0x0;
 hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
 hspi2.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
 hspi2.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
 hspi2.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
 hspi2.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
 hspi2.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
 hspi2.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
 hspi2.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
 hspi2.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
 hspi2.Init.IOSwap = SPI_IO_SWAP_DISABLE;
 if (HAL_SPI_Init(&hspi2) != HAL_OK)
 {
  Error_Handler();
 }
}



■SPIの通信
SPIの通信部分ではHALライブラリを使用せずにレジスタ、バッファに直接アクセスすることで同時にSPIを使用することができます。


HAL_GPIO_WritePin(CS_Port,CS_Pin,0);

//今回は6バイトの送受信を行うため、サイズに6を設定します。
MODIFY_REG(SPI1_HANDLE.Instance->CR2, SPI_CR2_TSIZE, 6);
MODIFY_REG(SPI2_HANDLE.Instance->CR2, SPI_CR2_TSIZE, 6);

//SPI通信有効化
__HAL_SPI_ENABLE(&SPI1_HANDLE);
__HAL_SPI_ENABLE(&SPI2_HANDLE);

//SPIスタート、マスタのみ
SET_BIT(SPI1_HANDLE.Instance->CR1, SPI_CR1_CSTART);//Only Master

//送信バッファに送信データを格納。なお、FIFIOは最大16Byte
for(size_t spiIndex=0;spiIndex<6;spiIndex++)
{

  *(__IO uint8_t *)&SPI1_HANDLE.Instance->TXDR = WriteData[0][spiIndex];
  *(__IO uint8_t *)&SPI2_HANDLE.Instance->TXDR = 0x00;

}
//送信後、受信バッファに格納されるまで待機
while ((SPI1_HANDLE.Instance->SR & SPI_FLAG_EOT) == RESET);
//受信バッファの読み出し
for(size_t spiIndex=0;spiIndex<6;spiIndex++)
{
   ReadData[0][spiIndex]=*(__IO uint8_t *)&SPI1_HANDLE.Instance->RXDR;
   ReadData[1][spiIndex]=*(__IO uint8_t *)&SPI2_HANDLE.Instance->RXDR;
}

//各エラーフラグクリア
SPI1_HANDLE.Instance->IFCR=0xFFFFFFFF;
SPI2_HANDLE.Instance->IFCR=0xFFFFFFFF;

//SPI通信無効化
__HAL_SPI_DISABLE(&SPI1_HANDLE);
__HAL_SPI_DISABLE(&SPI2_HANDLE);


HAL_GPIO_WritePin(CS_Port,CS_Pin,1);

HALライブラリを使用せずに直接SPIのレジスタ、バッファにアクセスすることで同時に複数のSPI通信する方法を紹介しました。STM32F4シリーズ等では送信受信でバッファが共通のDRレジスタとなっていましたが、STM32H7シリーズ等では送受信で個別のバッファとなっています。アナログデバイセズのサイトではADCの接続方法に限らず、様々なレポートが公開されており、非常に興味深いと思いました。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年08月22日

Node-Redを用いたアナログ値読み込み

前回紹介したNode-RedからのI2Cデバイス制御する方法に続いて、応用例を紹介します。今回はGroveADCモジュールUSB-I2C変換アダプタを使用してNode-Redを用いてAD変換をしてみました。

GroveADCモジュールADC121C021を搭載しており、I2Cで簡単にAD変換することが可能です。初期化の際に連続変換モードに設定すれば、自動的に最新のAD変換値がレジスタに入るため、値を読み出すだけでAD変換値を得ることができます。今回はGroveADCモジュールには可変抵抗を接続して手動で値を変えられるようにしました。

また、Node-Redのdashboardパレットを追加すると簡単に可視化も可能です。今回は簡易的にグラフとして電圧値を出力するUIを作成してみました。実際にNode-Redで作成してフローは下記です。

grove_adc_flows.jpg

作成したUIはこんな感じです。

grove_adc_UI.jpg

クリアボタンでグラフをクリアできます。また、スタートストップの切り替えスイッチも作成してみました。
今回のサンプルフローもこちらに追加しています。

USB-I2C変換アダプタとNode-Redを用いて簡単にAD変換ができました。光センサや温度センサなどアナログで出力する様々なセンサを接続して取り込んでみてください。なお、USB-I2C変換アダプタはUSB電源からの給電で動作するため、USBポート(特にパッシブUSBハブ)によっては電源電圧が低く、4V~5V付近のアナログ値を得られない場合があります。その場合はPC直のUSBポートか、ACアダプタ付きのUSBハブ等を使用して電圧が得られるUSBポートを使用してください。


posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年08月01日

STM32マイコン UART受信時必須処理 その2

以前、STM32マイコンでUARTの受信をする際に必須となるエラー処理について紹介しました。その際はフレーミングエラーが生じており、HAL_UART_Abort関数でクリアする方法を紹介しました。

今回は長いデータを連続して送信する際にオーバーランエラーが発生した際の対処方について紹介します。HAL_UART_Abort関数だけオーバーランエラーが発生した際にクリアされず、MPUがハングする場合がありました。環境としてはSTM32H743Z、STM32H7 Cube MCU Packeage 1.7です。

HAL_UART_Abort関数内では下記のようにエラー処理されており、オーバーランエラー、ノイズエラー、パリティーエラー、フレーミングエラーのクリア処理がされています。一見するとオーバーランエラーが解消されそうに見えますが、MPUがハングする現象がありました。

__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF | UART_CLEAR_NEF | UART_CLEAR_PEF | UART_CLEAR_FEF);


HAL_UART_Abort関数内のフラグだけでは十分でないようです。試行錯誤した結果、下記のように受信フラグやオーバーランフラグをクリアすると正常動作に復帰することが確認できました。

if ( __HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) )
{
    __HAL_UART_CLEAR_FLAG(&huart1,
                                              UART_CLEAR_NEF |
                                              UART_CLEAR_OREF |
                                              UART_FLAG_RXNE |
                                              UART_FLAG_ORE);
}

UART_FLAG_OREエラーが発生する場合は受信フラグやオーバーランフラグ自体をクリアする必要があるようです。エラーでUART受信できなくなるだけでなく、MPUがハングするような現象が発生したため、原因特定に時間を要してしまいました。最初はmalloc等のメモリ関連が原因かと思いましたが、UART単体でも異常が発生したため、UART起因と判明しました。UART回りはエラーケースが様々でなかなか厄介です。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年07月11日

iCE40HX8K RISC-V導入

今更な感じもしますが、命令セットアーキテクチャ (ISA)がオープンとなっている RISC-Vを試食してみました。ターゲットはiCE40-HX8Kを用いて、こちらのサイトの手順を参考に試食してみました。

手順についてはサイトに詳細に書かれているため省略しますが、hx8kdemoを実装した際のビルド結果を下記に記します。

=== hx8kdemo ===
Number of wires: 4536
Number of wire bits: 9050
Number of public wires: 4536
Number of public wire bits: 9050
Number of memories: 0
Number of memory bits: 0
Number of processes: 0
Number of cells: 7053
SB_CARRY 961
SB_DFF 175
SB_DFFE 651
SB_DFFESR 538
SB_DFFESS 58
SB_DFFNSR 4
SB_DFFSR 216
SB_DFFSS 6
SB_IO 4
SB_LUT4 4434
SB_RAM40_4K 6

サンプルコードでは約7000セル、LUTは約4500個の使用率でした。


RISCV.jpg

iCE40-HX8KのUart機能を使用してTeratermから動作確認ができました。picorv32/picosoc内のfirmware.cがデモのソースコードとなっています。

ファームのみのビルドする場合は下記のコマンドでビルドできます。
make hx8kdemo_fw.elf

ファームのみの書き込みする場合は下記のコマンドで書き込みできます。
sudo make hx8kprog_fw

iCE40-HX8Kで試食する前にMACHXO3でRISC-Vの実装を検討しましたが、そのままではRAM容量やクロックが確保できず諦めていました。海外等では工夫してMACHXO3でRISC-Vを実装した例がありましたが、コードが公開されていませんでした。最近、MACHXO3の新シリーズのMACHXO3DでLatticeが公式にRISC-VのIP Coreを公開されました。3.3Vで駆動可能なMACHXO3はiCE40-HX8Kよりも魅力が大きいため、今後、MACHXO3Dの評価ボードで試食してみたいと思います。

posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年07月04日

ライブラリ追加方法

今回はEclipseベースの開発環境(SW4STM32、STM32CubeIDEなど)でリンカライブラリ(*.lib)ファイルや静的ライブラリ(*.a)ファイル等のライブラリを追加する方法について紹介します。

すべてのコードが公開されている場合はリンカライブラリ(*.lib)ファイルや静的ライブラリ(*.a)ファイル等を読み込む必要はありませんが、コードが公開されていない場合にメーカ等からリンカライブラリ(*.lib)ファイルや静的ライブラリ(*.a)ファイル等のみ提供される場合があります。このような場合にライブラリを追加してリンカ生成時にユーザー側のコード内の関数と結合させてバイナリファイルを生成します。


@ライブラリ配置
 今回はプロジェクトフォルダの直下にライブラリファイルを置きます。

Aライブラリ追加設定
 プロジェクトファイルのプロパティを開き(Alt+Enter)、「C/C++Build」の「Settings」、「Tool Settings」「MCU GCC Linker」内のLibrariesから追加します。

img1.jpg


 まず、「Library search path」の設定を下記のように設定し、プロジェクトフォルダ直下を見に行く設定にします。

"${workspace_loc:/${ProjName}}"

 また、追加したライブラリを「Libraries」に追加します。ここでポイントはライブラリ名の前に「:」を追加する点です。

:***.LIB

img2.jpg

Bヘッダファイル追加
 ライブラリファイル内の関数を定義しているヘッダファイルを必要に応じて追加します。プロジェクトフォルダ内のIncフォルダに追加します。

Cその他
 ライブラリファイルが特定のArmコアに依存している場合、下記のようなビルドエラーが発生する場合があります。下記のエラーの場合はライブラリファイルがFPUなしのMPUをターゲットにしているため、FPU有のMPUでビルドしようとして発生するエラーです。このような場合はライブラリをFPU対応に書き換えることは難しいため、FPUなしとしてビルドします。

/bin/ld.exe: error: *.elf uses VFP register arguments *.o does not
/bin/ld.exe: failed to merge target specific data of file *.o

 プロジェクトファイルのプロパティを開き(Alt+Enter)、「C/C++Build」の「Settings」、「MPU Settings」内の「Floating-point ABI」を「hard」から「soft」に切り替えます。

img3.jpg


メーカからすべてのコードや情報が公開されている状態が理想的ですが、様々な制約で公開されていない場合はこのような方法で組み込むことができます。

posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする