1. 動機・目標
DWM3000を使っていく中でリアルタイムでログを取得する必要性が出てきたので,今まで使っていたJ-Link RTT Viewerではなく,UARTを使ってログをシリアル経由で出力できたら便利だなと思い,このセッティングを行うに至りました.
目標は,nRF52840-DKのログをUART経由で出力することです.よろしくお願いします.
2. セットアップ&準備物
セットアップに関しては割愛します.記事 DWM3000EVBを用いたCIRの取得 を参照してください.
環境や準備物については上記の記事と同様で,セットアップ手順に関しても同様です.なお本実験は電波暗室で実施しています.
3. UART経由での出力
今回も,SES(SEGGER EMBEDDED STUDIO)のVer5.10aを用いてプログラムをビルドしていきます.なお,プログラムに関しても記事 DWM3000EVBを用いたCIRの取得 において用いているexampleプログラムとSDKを用いています.
3.1. main.cの修正
今回は主に,Source/main.c
を修正することでUART出力を実現します.main.c
を修正することで,どのexampleプログラムを用いても簡単にUART出力ができるようになります.
- まず,以下のように#includeを修正します.
#include <boards.h>
#include <deca_spi.h>
#include <examples_defines.h>
#include <port.h>
#include <sdk_config.h>
#include <stdio.h>
#include <stdlib.h>
// 以下追加部分
#include <stdarg.h>
// Nordic SDK includes
#include "app_uart.h"
#include "nrf_uart.h"
#include "nrf_delay.h"
詳細は割愛しますが,UART出力を行うためのSDKのAPPとUARTのAPI,出力テスト用のDelay関数を使うためです.
- 続いてUART用のバッファを用意します.以下を先ほどの
#include
の後などに追加してください.
// --- UART Configuration for uart_printf ---
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 64 /**< UART RX buffer size. */
// UART buffers
static uint8_t m_uart_tx_buf[UART_TX_BUF_SIZE];
static uint8_t m_uart_rx_buf[UART_RX_BUF_SIZE];
続いて,UARTを使うための関数を定義していきます.
- まずはerror_handlerから.
// UART event handler
// UART event handler
static void uart_event_handle(app_uart_evt_t * p_event)
{
switch (p_event->evt_type)
{
case APP_UART_COMMUNICATION_ERROR:
// APP_ERROR_HANDLER(p_event->data.error_communication);
// For uart_printf, we might not need to crash on communication error
//// uart_printf("UART Comm Error: 0x%X\r\n", p_event->data.error_communication);
break;
case APP_UART_FIFO_ERROR:
// APP_ERROR_HANDLER(p_event->data.error_code);
// uart_printf("UART FIFO Error: 0x%X\r\n", p_event->data.error_code);
break;
case APP_UART_TX_EMPTY:
// TX buffer is empty
break;
case APP_UART_DATA_READY:
// Data is available in RX FIFO (not used by uart_printf directly)
// Example: read and echo back
// uint8_t cr;
// while (app_uart_get(&cr) == NRF_SUCCESS)
// {
// app_uart_put(cr);
// }
break;
default:
break;
}
}
エラーに対する出力等は現状コメントしているので,動かなければ適宜コメントアウトしてください.
- 続いてinit
// Function to initialize UART for uart_printf
static void uart_init(void)
{
uint32_t err_code;
const app_uart_comm_params_t comm_params = {
.rx_pin_no = RX_PIN_NUMBER,
.tx_pin_no = TX_PIN_NUMBER,
.rts_pin_no = RTS_PIN_NUMBER,
.cts_pin_no = CTS_PIN_NUMBER,
.flow_control = APP_UART_FLOW_CONTROL_DISABLED,
.use_parity = false,
.baud_rate = NRF_UART_BAUDRATE_115200
};
app_uart_buffers_t buffers = {
.rx_buf = m_uart_rx_buf,
.rx_buf_size = sizeof(m_uart_rx_buf),
.tx_buf = m_uart_tx_buf,
.tx_buf_size = sizeof(m_uart_tx_buf)
};
err_code = app_uart_init(&comm_params, &buffers, uart_event_handle, APP_IRQ_PRIORITY_LOWEST); // Use LOWEST or LOW
APP_ERROR_CHECK(err_code); // Check for critical errors during init
}
出力のピンを決めて,バッファを指定して,ボーレートを指定して...というようなUARTに関する設定をし,UARTの初期化を行っています.
- 続いて,UART経由Ver.のprintf関数である
uart_ptinrf
を新たに定義します.以下の関数を追記してください.
// --- Custom uart_printf implementation ---
#define UART_PRINTF_BUFFER_SIZE 256
void uart_printf(const char *fmt, ...)
{
char buffer[UART_PRINTF_BUFFER_SIZE];
va_list args;
va_start(args, fmt);
// Use vsnprintf to format the string into the buffer
// It's safer as it prevents buffer overflows if the formatted string is too long.
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
if (len > 0)
{
// Ensure we don't write past the buffer, even if vsnprintf reported a larger potential length
int actual_len = (len < (int)sizeof(buffer)) ? len : (int)sizeof(buffer) - 1;
for (int i = 0; i < actual_len; i++)
{
// Loop until the character is successfully put into the UART TX FIFO
// This is a blocking call if the FIFO is full.
while(app_uart_put((uint8_t)buffer[i]) != NRF_SUCCESS);
}
}
}
引数にprintfしたい文字列を入れれば,通常のprintfと同様にログを吐き出すという仕様になっています.
詳細は割愛しますが,app_uart_put
に構成した文字列を放り込むことで,UART用のバッファに文字列を格納し,トリガーを立てることでUART出力させるというような感じです.
- これに合わせて,出力用関数であるtest_run_infoに以下の変更を加えましょう.
void test_run_info(unsigned char *data)
{
uart_printf("%s\n", data);
}
- 最後に,UARTを初期化する関数を
main.c
のint main(void)
内に記述します.デバッグ用にuart_printf
を用いた文字列出力も実装しておきます.
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* Initialize logs (optional, but good practice) */
//log_init(); // Initializes NRF_LOG, which might also use RTT or UART depending on sdk_config
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals (if attached). */
build_examples();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
bsp_board_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS);
/* Initialise nRF52840-DK GPIOs */
gpio_init();
/* Initialise the SPI for nRF52840-DK */
nrf52840_dk_spi_init();
/* Configuring interrupt*/
dw_irq_init();
/* Initialize custom UART for uart_printf */ // こちらを追加
uart_init();
// 起動確認と接続待ち用
bsp_board_led_on(0);
nrf_delay_ms(3000);
bsp_board_led_off(0);
nrf_delay_ms(100);
if (UNIT_TEST)
{
uart_printf("Starting unit tests via UART...\r\n"); // デバッグ用に追加
unit_test_main();
}
else
{
uart_printf("Starting example via UART...\r\n"); // デバッグ用に追加
// Run the selected example as selected in example_selection.h
example_pointer();
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
LEDが三秒間光った後に,UARTにテスト用の文字列が出力されるという仕様にしています.
3.2. 依存関係の解消
UARTを使うにあたって,APIを使うために依存関係を解消しなければなりません.
ということで,そのあたりの解決をしていきます.
-
まず,Project Explorerを利用して
nrf_drv_uart.c
ファイルとnrf_ringbuf.c
ファイル,nrfx_uarte.c
ファイルをSource/SDK/nRF_Drivers
に追加してください.nrf_drv_uart.c: (APIディレクトリ)\Build_Platforms\nRF52840-DK\SDK\integration\nrfx\legacy
nrf_ringbuf.c: (APIディレクトリ)\Build_Platforms\nRF52840-DK\SDK\components\libraries\ringbuf
nrfx_uarte.c: (APIディレクトリ)\Build_Platforms\nRF52840-DK\SDK\modules\nrfx\drivers\src
-
続いて,
app_uart_fifo.c
ファイルとapp_fifo.c
ファイルをSource/SDK/nRF_Libraries
に追加してください. -
続いて,ヘッダファイルの依存関係を解消します.
Project ExplorerからProject 'dw3000_api'を右クリックし,Optionを開いてください.
Optionを開くと以下のようなウィンドウが開きます.
Code->Preprocessor->User Include Dictionariesをダブルクリックし,以下のように記述します.
$(NordicSDKDir)/components/libraries/uart
$(NordicSDKDir)/modules/nrfx/drivers/include
これで必要なヘッダファイルを自動で#includeするようにします.
3.3. Configの設定
最後にConfigを設定します.
-
(APIディレクトリ)\Build_Platforms\nRF52840-DK\Source\config
内に存在するヘッダファイル,sdk_config.h
の以下の#defineに対して変更を加えます.
// 0->1に変更
#define UART_ENABLED 1
#define UART0_ENABLED 1
#define NRFX_UARTE_ENABLED 1
#define NRFX_UARTE0_ENABLED 1
#define APP_UART_ENABLED 1
// 1->0に変更
#define UART_LEGACY_SUPPORT 0
今回はUARTEを使うので,このようなconfigとしました.
3.4. exampleプログラム側の設定
今回はexampleプログラムとしてsimple_tx.c
を使用します.
- もともとあるtest_run_infoのすぐ下の行に以下のようにuart_printfを定義してください.
extern void test_run_info(unsigned char *data);
extern void uart_printf(const char *fmt, ...); // <-追加
この追加により,先ほどのmain.cでのtest_run_infoの訂正を行っていれば,exampleプログラム内のtest_run_info((unsigned char *)"TX Frame Sent");
の行やtest_run_info((unsigned char *)APP_NAME);
の部分がUART経由で出力されるようになるはずです.
4.テスト
4.1. exampleプログラムのビルドとダウンロード
作成したプログラムをビルド,ダウンロードしていきます.
(APIディレクトリ)\Src
の下に,どのexampleプログラムをビルドするかを選択するヘッダファイル(example_selection.h
)があります.この"#define TEST_SIMPLE_TX"の部分を以下のようにコメントアウトを消して保存してください.
- その後,SESでdw3000_api.emProjectを開き,Build > Set Active Build ConfigurationをReleaseに設定します.
- この操作が終わったら,送信用のDW3000を電波暗室内に設置し Build > Build and Runを選択してください.そうすると送信用のDW3000制御用マイコンにUWBパケットを送信するためのプログラムが書き込まれ,同時にUWBパケットを500msec間隔で送信し始めます.
4.2. TeraTermによるログ出力確認
TeraTermによって,仮想ポート経由でログが出力されているかを確認します.
- nRF-52840をPCにつなぐと,TeraTerm側から以下のようなポートが見えます.
このJLink CDC UART Portから仮想ポートにアクセスしてUARTの出力をとることができます.画像ではCOM28とCOM29のように二つありますが,どちらかから出力されます.多くの場合は下側です. - 正常にUARTによるログ出力が行われていれば,以下のようなログが取得できると思います.
5. 参考記事
DWM3000EVBを用いたUWB位置測位① ~セットアップ編~
DWM3000EVBを用いたUWB位置測位② ~PCデモ編~