Nordic社のBLEモジュールnRF51822のSPIモジュールの機能について解説したく思います。
ここではSPIはマスターとして使用する場合を考えます。
読者様はSPI機能については既に知っているという前提で以下の解説をします。
モジュール機能のアドレス割り当て
nRF51822のSPIモジュールには、マスターとして機能するSPI0とSPI1とスレーブとして機能するSPIS1があります。
SPI Slave機能のレジスタとSPI Master 1ch機能のレジスタとI2C 1ch機能のレジスタは同じアドレス0x40004000に割り当てられています。つまり、この3つの機能を同時に使うことはできません。
また、SPI Master 0ch機能のレジスタとI2C 0ch機能のレジスタは同じアドレス0x40003000に割り当てられています。つまり、この2つの機能を同時に使うことはできません。
ですがプログラムで機能を切り替えることで、1つのファームウェアで両方の機能が使えることを確認しています。
(切り替え方法については別途記事にする予定です。)
設定レジスタ
シーケンス
SPIマスター機能は、MISO信号、MOSI信号、SCLK信号を制御します。
SPI制御に必要なチップセレクト信号はユーザーがどのスレーブデバイスを使うか指定するためのものなので、SPIモジュールは自動制御しません。
ユーザーがGPIOモジュールを用いてON/OFF制御してください。
SPI通信は以下のシーケンスで実行されます。
①TXDレジスタに1byte目を書き込むと、直後に2byte目もTXDレジスタに書き込まれ、1byte目の送信が始まる。
②データを送信すると同時に、RXDレジスタにデータを受信する。
RXDレジスタを読み込むと、次のデータがRXDレジスタに格納される。
③READYイベントがRXDレジスタにデータを格納するたびに発生する。
RXDレジスタを読み込まなければ、いつまで待ってもREADYイベントは発生せず、次のデータ送受信は開始されない。
④TXDレジスタに送信データがなくなると動作は完了する。
波形
実際にSPI通信を行った時の波形を以下に示します。
プログラム:SPI送受信処理
以下はNordic社が公開しているサンプルプログラムからの抜粋です。
https://devzone.nordicsemi.com/attachment/f3b250dd5a8d462aca54b23d4a4d2b2c
プログラム中でSPI送受信処理を行う場合は、この関数を使います。
uint32_t spi_master_send_recv(const spi_master_hw_instance_t spi_master_hw_instance,
uint8_t * const p_tx_buf, const uint16_t tx_buf_len,
uint8_t * const p_rx_buf, const uint16_t rx_buf_len)
{
#if defined(SPI_MASTER_0_ENABLE) || defined(SPI_MASTER_1_ENABLE)
volatile spi_master_instance_t * p_spi_instance = spi_master_get_instance(
spi_master_hw_instance);
APP_ERROR_CHECK_BOOL(p_spi_instance != NULL);
uint32_t err_code = NRF_SUCCESS;
uint16_t max_length = 0;
uint8_t nested_critical_region = 0;
//Check if disable all IRQs flag is set //割込み無効処理
if (p_spi_instance->disable_all_irq)
{
//Disable interrupts.
APP_ERROR_CHECK(sd_nvic_critical_region_enter(&nested_critical_region));
}
else
{
//Disable interrupt SPI.
APP_ERROR_CHECK(sd_nvic_DisableIRQ(p_spi_instance->irq_type));
}
//Initialize and perform data transfer
if (p_spi_instance->state == SPI_MASTER_STATE_IDLE) //IDLE状態かの確認
{
max_length = (rx_buf_len > tx_buf_len) ? rx_buf_len : tx_buf_len; //最大送受信バイト数確認(送信データと受信データのどちらが多いかを確認して、多いほうに合わせる。)
if (max_length > 0)
{
p_spi_instance->state = SPI_MASTER_STATE_BUSY; //IDLE状態からBUSY状態へ遷移
p_spi_instance->bytes_count = 0; //送受信カウンタクリア
p_spi_instance->started_flag = false; //開始フラグクリア
p_spi_instance->max_length = max_length; //最大送受信バイト数設定
/* Initialize buffers */
spi_master_buffer_init(p_tx_buf, //送信バッファ初期化
tx_buf_len,
&(p_spi_instance->p_tx_buffer),
&(p_spi_instance->tx_length),
&(p_spi_instance->tx_index));
spi_master_buffer_init(p_rx_buf, //受信バッファ初期化
rx_buf_len,
&(p_spi_instance->p_rx_buffer),
&(p_spi_instance->rx_length),
&(p_spi_instance->rx_index));
nrf_gpio_pin_clear(p_spi_instance->pin_slave_select); //CS信号アサート
spi_master_send_initial_bytes(p_spi_instance); //1byte目送信開始
}
else //最大送受信バイト数が負ならエラー
{
err_code = NRF_ERROR_INVALID_PARAM;
}
}
else //IDLE状態でないならば別の送受信処理実行中なのでBUSYエラー
{
err_code = NRF_ERROR_BUSY;
}
//Check if disable all IRQs flag is set. //割込み有効処理
if (p_spi_instance->disable_all_irq)
{
//Enable interrupts.
APP_ERROR_CHECK(sd_nvic_critical_region_exit(nested_critical_region));
}
else
{
//Enable SPI interrupt.
APP_ERROR_CHECK(sd_nvic_EnableIRQ(p_spi_instance->irq_type));
}
return err_code;
#else
return NRF_ERROR_NOT_SUPPORTED;
#endif
}
プログラム:READYイベント割込み処理
void SPI0_TWI0_IRQHandler(void)
{
if ((NRF_SPI0->EVENTS_READY == 1) && (NRF_SPI0->INTENSET & SPI_INTENSET_READY_Msk))
{
NRF_SPI0->EVENTS_READY = 0; //イベントクリア
volatile spi_master_instance_t * p_spi_instance = spi_master_get_instance(SPI_MASTER_0); //インスタンスが0か1かを確認
spi_master_send_recv_irq(p_spi_instance); //送受信割込み処理
}
}
static __INLINE void spi_master_send_recv_irq(volatile spi_master_instance_t * const p_spi_instance)
{
APP_ERROR_CHECK_BOOL(p_spi_instance != NULL); //インスタンスが無効でないことの確認
p_spi_instance->bytes_count++; //送受信カウンタ累積
//シーケンス図のAで実行される処理:初回READYイベント発生処理
if (!p_spi_instance->started_flag) //スタートフラグが0の場合
{
p_spi_instance->started_flag = true; //スタートフラグ アサート
spi_master_signal_evt(p_spi_instance,
SPI_MASTER_EVT_TRANSFER_STARTED,
p_spi_instance->bytes_count);
}
uint8_t rx_byte = p_spi_instance->p_nrf_spi->RXD; //RXDレジスタ用ローカル変数作成
//シーケンス図のBで実行される処理:RXDレジスタ受信データ読み込み処理
if ((p_spi_instance->p_rx_buffer != NULL) && //RXDレジスタに受信データがある場合
(p_spi_instance->rx_index < p_spi_instance->rx_length))
{
p_spi_instance->p_rx_buffer[p_spi_instance->rx_index++] = rx_byte; //RXDレジスタ読み込み&受信カウンタ累積
}
//シーケンス図のCで実行される処理:TXDレジスタ送信データ格納処理
if (p_spi_instance->tx_index < p_spi_instance->max_length) //送信データがまだある場合
{
p_spi_instance->p_nrf_spi->TXD = ((p_spi_instance->p_tx_buffer != NULL) &&
(p_spi_instance->tx_index < p_spi_instance->tx_length)) ?
p_spi_instance->p_tx_buffer[p_spi_instance->tx_index] :
SPI_DEFAULT_TX_BYTE; //送信データorダミーデータ送信
(p_spi_instance->tx_index)++; //送信カウンタ累積
}
//シーケンス図のDで実行される処理:最終READYイベント処理
if (p_spi_instance->bytes_count >= p_spi_instance->max_length)
{
nrf_gpio_pin_set(p_spi_instance->pin_slave_select); //CS信号ネゲート
uint16_t transmited_bytes = p_spi_instance->tx_index;
spi_master_buffer_release(&(p_spi_instance->p_tx_buffer), &(p_spi_instance->tx_length)); //送信バッファクリア
spi_master_buffer_release(&(p_spi_instance->p_rx_buffer), &(p_spi_instance->rx_length)); //受信バッファクリア
p_spi_instance->state = SPI_MASTER_STATE_IDLE;
spi_master_signal_evt(p_spi_instance, SPI_MASTER_EVT_TRANSFER_COMPLETED, transmited_bytes); //送受信完了処理
}
}