ぼくの技術日誌

日誌って銘打っていますが、更新頻度が…

Arduino M0 Proのシリアル通信

はじめに

前回はAtmelStudioでArduino M0 Pro上のLEDを点滅させました。
今回は、シリアル入出力とprintfが使えることが確認できたので、その方法を書いておこうと思います。


この記事の実行環境は下記のとおりです。

ホストOS:Windows7 x64
開発ソフト:AtmelStudioバージョン 7.0.634
ターゲット:Arduino M0 Proファームウェアバージョン 2.10

シリアル通信

ASFの公式ドキュメントQuick Start Guide for SERCOM USART - Basic を調べると、SERCOM USARTというシリアル通信のドライバが見つかりました。
サンプルコードも書いてあります!

しかし、サンプルコードをそのまま貼り付けてもEDBG_CDC_~の定義名全てが見当たらず、コンパイルができません。
調べると、公式のフォーラムAtmel Communityの記事SAMD21 Serial Port Setup and Hello World | AVR Freaksにヒントがありました。

記事抜粋

config_usart.mux_setting = USART_RX_1_TX_0_XCK_1;
config_usart.pinmux_pad0 = PINMUX_PA16D_SERCOM3_PAD0;
config_usart.pinmux_pad1 = PINMUX_PA17D_SERCOM3_PAD1;
config_usart.pinmux_pad2 = PINMUX_UNUSED;
config_usart.pinmux_pad3 = PINMUX_UNUSED;   

pinmux_pad0~3には、TXまたはRXの接続を書くようです。使わないものには未使用の定義名PINMUX_UNUSEDが書かれています。
また、mux_settingはpinmux_pad0~3がTXなのかRXなのかを書くようです。公式ドキュメントSERCOM USART MUX Settingsに設定の詳細が書かれてありました。

pinmux_pad0~3の接続先を探るべく、Arduino M0 Proの回路図を読んでみます。
回路図によると、EDBGの配線は下記のような接続になっています。
TX:PB22_SERCOM5_PAD2
RX:PB23_SERCOM5_PAD3

また、Arduinoではお馴染み、Pin0とPin1もシリアル入出力が割り当てられています。

自前のアプリケーションでシリアル通信を使うのはPin0とPin1を、デバッグ用途にEDBG側を使えば良さそうですね。
TX:PINMUX_PA11D_SERCOM2_PAD3;
RX:PINMUX_PA10D_SERCOM2_PAD2;

コード補完で上記に近い定数名が出ます。
これで、シリアルターミナルを別に立ち上げ、プログラムを実行してみるとシリアルターミナルに文字が出てきました!!

Pin0とPin1を使ったシリアル入出力のサンプル

#include <asf.h>
#include <compiler.h>
#include <board.h>
#include <conf_board.h>
#include <port.h>

//TX is PA11D_SERCOM2_PAD3
//RX is PA10D_SERCOM2_PAD2
 static struct usart_module usart_instance;
 static void configure_console(void)
{
	struct usart_config usart_conf;
	usart_get_config_defaults(&usart_conf);
	usart_conf.mux_setting = USART_RX_3_TX_2_XCK_3;//http://asf.atmel.com/docs/latest/samd21/html/asfdoc_sam0_sercom_usart_mux_settings.html
	usart_conf.pinmux_pad0 = PINMUX_UNUSED;
	usart_conf.pinmux_pad1 = PINMUX_UNUSED;
	usart_conf.pinmux_pad2 = PINMUX_PA11D_SERCOM2_PAD3;
	usart_conf.pinmux_pad3 = PINMUX_PA10D_SERCOM2_PAD2;
	usart_conf.baudrate    = 9600;
	
	while (usart_init(&usart_instance, SERCOM2, &usart_conf) != STATUS_OK) {
	}

	usart_enable(&usart_instance);
}

int main (void)
{
	system_init();

	configure_console();

	/* Insert application code here, after the board has been initialized. */
	struct port_config pin_conf;
	port_get_config_defaults(&pin_conf);
	pin_conf.direction  = PORT_PIN_DIR_OUTPUT;
	port_pin_set_config(PIN_PA17, &pin_conf);
		
	//http://asf.atmel.com/docs/3.16.0/sam0.drivers.tc.unit_test.samd21_xplained_pro/html/asfdoc_sam0_sercom_usart_basic_use_case.html
	uint8_t string[] = "Hello\r\n";
	usart_write_buffer_wait(&usart_instance, string, sizeof(string));
	uint16_t temp;
	while (1) {
		if (usart_read_wait(&usart_instance, &temp) == STATUS_OK) {
			while (usart_write_wait(&usart_instance, temp) != STATUS_OK) {
			}
		}
	}
}

EDBGを使ったシリアル入出力のサンプル

#include <asf.h>
#include <compiler.h>
#include <board.h>
#include <conf_board.h>
#include <port.h>

//TX is PB22_SERCOM5_PAD2
//RX is PB23_SERCOM5_PAD3
 static struct usart_module usart_instance;
 static void configure_console(void)
{
	struct usart_config usart_conf;
	usart_get_config_defaults(&usart_conf);
	usart_conf.mux_setting = USART_RX_3_TX_2_XCK_3;//http://asf.atmel.com/docs/latest/samd21/html/asfdoc_sam0_sercom_usart_mux_settings.html
	usart_conf.pinmux_pad0 = PINMUX_UNUSED;
	usart_conf.pinmux_pad1 = PINMUX_UNUSED;
	usart_conf.pinmux_pad2 = PINMUX_PB22D_SERCOM5_PAD2;//PINMUX_PA11D_SERCOM2_PAD3;
	usart_conf.pinmux_pad3 = PINMUX_PB23D_SERCOM5_PAD3;//PINMUX_PA10D_SERCOM2_PAD2;
	usart_conf.baudrate    = 9600;
	
	while (usart_init(&usart_instance, SERCOM5/*SERCOM2*/, &usart_conf) != STATUS_OK) {
	}

	usart_enable(&usart_instance);
}

int main (void)
{
	system_init();

	configure_console();

	/* Insert application code here, after the board has been initialized. */
	struct port_config pin_conf;
	port_get_config_defaults(&pin_conf);
	pin_conf.direction  = PORT_PIN_DIR_OUTPUT;
	port_pin_set_config(PIN_PA17, &pin_conf);
		
	//http://asf.atmel.com/docs/3.16.0/sam0.drivers.tc.unit_test.samd21_xplained_pro/html/asfdoc_sam0_sercom_usart_basic_use_case.html
	uint8_t string[] = "Hello\r\n";
	usart_write_buffer_wait(&usart_instance, string, sizeof(string));
	uint16_t temp;
	while (1) {
		if (usart_read_wait(&usart_instance, &temp) == STATUS_OK) {
			while (usart_write_wait(&usart_instance, temp) != STATUS_OK) {
			}
		}
	}
}

上記ふたつのサンプルを実行すれば、シリアルターミナル側から文字を送ると、Arduino M0 Proで受信内容を返送します。

さらに、公式ドキュメントQuick Start Guide for SERCOM USART - Callback にはコールバックを使ったシリアル通信のサンプルコードもありました。

シリアル受信・送信にコールバック関数を使うサンプル

#include <asf.h>
#include <compiler.h>
#include <board.h>
#include <conf_board.h>
#include <port.h>

#define MAX_RX_BUFFER_LENGTH   5
volatile uint8_t rx_buffer[MAX_RX_BUFFER_LENGTH];

//TX is PB22_SERCOM5_PAD2
//RX is PB23_SERCOM5_PAD3
 static struct usart_module usart_instance;
 static void configure_console(void)
{
	struct usart_config usart_conf;
	usart_get_config_defaults(&usart_conf);
	usart_conf.mux_setting = USART_RX_3_TX_2_XCK_3;//http://asf.atmel.com/docs/latest/samd21/html/asfdoc_sam0_sercom_usart_mux_settings.html
	usart_conf.pinmux_pad0 = PINMUX_UNUSED;
	usart_conf.pinmux_pad1 = PINMUX_UNUSED;
	usart_conf.pinmux_pad2 = PINMUX_PB22D_SERCOM5_PAD2;//PINMUX_PA11D_SERCOM2_PAD3;
	usart_conf.pinmux_pad3 = PINMUX_PB23D_SERCOM5_PAD3;//PINMUX_PA10D_SERCOM2_PAD2;
	usart_conf.baudrate    = 9600;
	
	while (usart_init(&usart_instance, SERCOM5/*SERCOM2*/, &usart_conf) != STATUS_OK) {
	}

	usart_enable(&usart_instance);
}

void usart_read_callback(struct usart_module *const usart_module)
{
	usart_write_buffer_job(&usart_instance,
	(uint8_t *)rx_buffer, MAX_RX_BUFFER_LENGTH);
}
void usart_write_callback(struct usart_module *const usart_module)
{
	port_pin_toggle_output_level(PIN_PA17);
}

void configure_usart_callbacks(void)
{
	usart_register_callback(&usart_instance, usart_write_callback, USART_CALLBACK_BUFFER_TRANSMITTED);
	usart_register_callback(&usart_instance, usart_read_callback, USART_CALLBACK_BUFFER_RECEIVED);
	usart_enable_callback(&usart_instance, USART_CALLBACK_BUFFER_TRANSMITTED);
	usart_enable_callback(&usart_instance, USART_CALLBACK_BUFFER_RECEIVED);
}

int main (void)
{
	system_init();

	/* Insert application code here, after the board has been initialized. */
	struct port_config pin_conf;
	port_get_config_defaults(&pin_conf);
	pin_conf.direction  = PORT_PIN_DIR_OUTPUT;
	port_pin_set_config(PIN_PA17, &pin_conf);

	configure_console();
	configure_usart_callbacks();
	
	system_interrupt_enable_global();
	uint8_t string[] = "Hello World!\r\n";
	usart_write_buffer_wait(&usart_instance, string, sizeof(string));
	while (true) {
		usart_read_buffer_job(&usart_instance, (uint8_t *)rx_buffer, MAX_RX_BUFFER_LENGTH);
	}
}

printfを使う方法

ログを出すときなど、printfで出力を整形したいということは多いはず。
printfの出力先をUARTにつなぐことができるらしいとは知っていますが、自分ではやったことがありません。

Areduino M0 Proのprintf出力について調べてみると、ASFを使えば簡単に接合できそうなのでやってみました。

atmel sensor using printf - Stack Overflowの手順に従って、ASF WizardでStandard serial I/OとUSARTをプロジェクトに追加します。
configure_consoleを以下のように書き換えます。

printfを使ってEDBG側にシリアル出力を行うサンプル

#include <asf.h>
#include <compiler.h>
#include <board.h>
#include <conf_board.h>
#include <port.h>

//TX is PB22_SERCOM5_PAD2
//RX is PB23_SERCOM5_PAD3
 static struct usart_module usart_instance;
 static void configure_console(void)
{
	struct usart_config usart_conf;
	usart_get_config_defaults(&usart_conf);
	usart_conf.mux_setting = USART_RX_3_TX_2_XCK_3;//http://asf.atmel.com/docs/latest/samd21/html/asfdoc_sam0_sercom_usart_mux_settings.html
	usart_conf.pinmux_pad0 = PINMUX_UNUSED;
	usart_conf.pinmux_pad1 = PINMUX_UNUSED;
	usart_conf.pinmux_pad2 = PINMUX_PB22D_SERCOM5_PAD2;//PINMUX_PA11D_SERCOM2_PAD3;
	usart_conf.pinmux_pad3 = PINMUX_PB23D_SERCOM5_PAD3;//PINMUX_PA10D_SERCOM2_PAD2;
	usart_conf.baudrate    = 9600;
	
	stdio_serial_init(&usart_instance, SERCOM5, &usart_conf);

	usart_enable(&usart_instance);
}

int main (void)
{
	system_init();

	configure_console();

	/* Insert application code here, after the board has been initialized. */
	struct port_config pin_conf;
	port_get_config_defaults(&pin_conf);
	pin_conf.direction  = PORT_PIN_DIR_OUTPUT;
	port_pin_set_config(PIN_PA17, &pin_conf);

	int cnt = 0;
	printf("Hello\r\n");
	while(1){
		printf("%d\r\n", cnt);
		port_pin_set_output_level(PIN_PA17, true);
		pseudo_usleep(1000);
		port_pin_set_output_level(PIN_PA17, false);
		pseudo_usleep(1000);
		cnt++;
	}
}

1000msec毎にLEDの点滅を繰り返しつつ、点滅の回数をシリアルで出力します。

さいごに

ASFを使えば、案外簡単にシリアル入出力とprintf利用ができでとても驚きました。
それは、公式のASFドキュメントが親切丁寧であり、フォーラムに情報が多数寄せられるためだと思っています。ありがたい!
今後はUSB Host/Device機能を使って、キーボードやボリュームコントローラを作ってみたいです。難しいかなぁ。