FusionPCBでプリント基板を製造した話 その2(再生産)

前回、「FusionPCBでプリント基板を製造した話」を書いた後、再生産してもらえることになりました。
結論から言うと、2度再生産してもらいましたが、大変満足のいく仕上がりになりました。
本記事では、再生産の経過について書いておきます。

再生産(1度目)

fusion.jp@seeed.cc(日本用の問い合わせ窓口)に対し、メールで状況を伝えると、クーポンが送られてきました。クーポンを使って再度発注する、というスタイルのようです。
なお、このクーポンは送料にも適用できるため、ほぼ無料(※)で再度発注ができるようになっています。

※実際にはシステム上、下のようにUSD0.01必要となります。

支払金額(製造費と送料がほぼ0となる)

スケジュールは下記のように動きました。
前回と比べ、とても素早く対応いただき、発注から1週間で納品されています。

2017/08/30(水):発注完了
2017/09/01(金):ガーバーチェック終了、製造開始
2017/09/04(月):発送(DHLを使用)
2017/09/06(水):到着

しかし、着荷した基板には残念な点がありました。

上のように、絵の部分のレジストが剥がれているのです。(10枚中10枚とも、同じように)
前回の不具合(傷、穴やシルクのズレ)は全て解消されていただけに、惜しいですね…

再生産(2回目)

絵の部分には細かな線があり、デザインルールに沿っていません(つまり保証外のはず)。
理由を問い合わせてみると、次のような回答を頂きました。

・黒のレジストインクは、皺や剥がれが発生しやすい特性を持つ。
・絵の部分が細い線が構成されており、現像液に洗い流された可能性がある。

やはり、デザインルールを満たしていない部分は難しいようです。
しかし今回は、無料で再生産して頂けることになりました!

やり取りをしたのが9/8頃で、暫く音沙汰が無かったのですが、9/23(土)に無事に着荷しました。
再生産してもらった基板がこちらです。

とても綺麗に作られており、全く問題はありませんでした。

終わりに

fusionpcbは、サポートも良く、好感が持てるサービスだと思います。
他の安価な中華基板サービスと同じように「安かろう悪かろう」ではなく、「100%」品質保証ということなので、今後はfusionpcbをメインに使用していこうと思います。

ただし前回も述べたように、デザインルールに余裕を持たせることや、到着後に必ず品質をチェックする必要はあるでしょう。


FusionPCBでプリント基板を製造した話(2017年8月)

Maker界隈では有名なFusionPCB(Seeed)に、プリント基板を発注してみました。日本語サイトは最近出来たようです。発注方法などはネット上に多くの情報がありますので、ここでは次にフォーカスしようと思います。

  • スルーホールのメッキ有無(PTH, NPTH)の指定方法
  • 発注~到着まで
  • 基板の品質

1.スルーホールのメッキ有無の指定

発注時に迷ったのが、PTH(メッキ有りスルーホール)とNPTH(メッキ無しスルーホール)の指定方法です。下記のFAQの通りにやれば良いようです。

How can i do if i do not required to make plated through holes?

Plated holes:
Circuit layer: holes have copper
Solder mask layer: holes have openings

Non-plated holes:
Circuit layer: holes have no copper
Solder mask layer: openings don’t matter

FAQに従い、ガーバーデータ上で、

  • PTH→穴を銅箔で覆い、穴上のソルダーマスクを指定する(レジストを剥がす)
  • NPTH→穴の銅箔を剥がす。ソルダーマスクの有無は関係なし。

とすればOKです。

PTHは、ガーバーデータを下のようにしました。

NPTHの場合は、ドリル穴のみにしました。

また、メッキの有無でドリルファイルを分ける必要はあるか、と問い合わせた所、「ドリルファイルは1つにまとめても、2つに分けてもよい。」とのことでしたので、今回は2つのファイルに分けて発注しました。

※ガーバーデータの生成方法については、fusionpcb.jpのよくある質問が参考になります。私はKiCadを使っており、こちらの記事に従って出力しました。

2.発注~到着まで

今回は同じアートワークの基板を、10枚ずつ、2種類の仕様で発注しました。値段と主な仕様は次の通りです。

■青レジスト基板(HASL):4.90USD

外形 層数 レジスト色 表面処理 銅箔厚 板厚 最小穴径 パターン幅/間隔
64mm×67mm 2 HASL 1oz. 1.6mm 0.3mm 6/6mil

■黒レジスト基板(ENIG):29.9USD

外形 層数 レジスト色 表面処理 銅箔厚 板厚 最小穴径 パターン幅/間隔
64mm×67mm 2 ENIG 1oz. 1.6mm 0.3mm 6/6mil

100mm×100mm以内であればとても安価になるので、プロトタイピングに大助かりです。

この条件で発注したところ、次のスケジュールで動きました。

2017/08/14(月):発注完了
2017/08/15(火):ガーバーチェック終了、製造開始
2017/08/25(金):発送
2017/08/27(日):到着
※発送にはDHLを使用

他社のサービスに比べ、製造スケジュールは遅めですが、こんなものでしょう。
FusionPCBはシンガポール経由になるので輸送が遅い、という情報がネットにありましたが、
今回は香港経由になり、発送から到着までは速かったです。改善されているようで好印象です。

3.基板品質

レジストが青と黒の2種類を発注しましたので、それぞれを比較します。

結論から言うと、青レジストの基板は問題ありませんでしたが、残念ながら黒レジストの基板は10枚中7枚に問題がありました。

以下の写真では敢えて、一番悪い状態のものを選んでいます。

全体


部品面と裏面の写真です。
数量はピッタリ10枚ずつでした。

シルク


青:特に問題はありません。印刷も鮮明で読みやすいです。
黒:パッドの上に被っています。これははんだのノリが悪くなるのでNGです。デザインルールを守り、シルク-パッド間のクリアランスは確保しているのですが…。
ちなみに、シルクがパッドに重なりそうな箇所は、FusionPCB側で余裕をもってシルクカットしているらしい(明確なソースは見つけてない)のですが、ズレが大きすぎると吸収できませんよね…。

スルーホール


丸穴→ドリル径0.6mm, 外径0.95mm
長穴→0.8mm/2.2mm

青:特に問題はありません。
黒:丸穴がズレているため、アニュラリング(スルーホールのパターン幅)が細くなり、パターンが切れそうです。
デザインルールを守り、アニュラを0.15mm以上にして設計していたのですが、もっと太めにしておくのが良さそうですね。

表面実装用パッド


0.8mmピッチのQFPパッドの写真です。
青・黒ともに、レジストにズレが見られますが、許容範囲内だと思います。

ロゴと絵


予想より綺麗な仕上がりです。線(レジスト)が崩れている箇所がありますが、元々デザインルールを度外視している部分のため、仕方の無い所ではあります。

余談ですが、もとの絵は↓に投稿しています。

青木れいかさん by けしかん on pixiv

その他


黒レジスト基板のうち1枚だけ、ベタGNDの一部が露出していました。レジストが剥離した状態でメッキされたのでしょうか。これはショートを引き起こす可能性があるのでNGです。

4.総評

安価で使いやすいと思います。複雑な回路の試作には重宝するでしょう。

ただし、「穴・シルク・レジストのズレありき」で設計するのが良さそうです。メーカーのデザインルールよりも余裕を持たせると、問題が出にくくなると思います。(メーカーのデザインルールって何だろう…)

また、ロットによる品質のばらつきは大きいかもしれません。今回の場合、青レジストは良品でしたが、黒レジストは10枚中7枚が不良でした。届いたらきちんと検品した方が良いです。不良品については再生産して頂くよう、お願いしている最中です。

2017/8/29追記:再生産してもらうことになりました。到着したら、本記事に追記します。

2017/9/30追記:再生産の経過について、記事を書きました。

5.余談

基板の撮影にはUSBマイクロスコープと、CameraViewer(橋本 直さん)を使用しております。


[STM32F7で作るMIDI音源] その4 MIDIメッセージの受信とリングバッファ

自作MIDI音源「CureSynth」製作記事の一覧はこちら
回路図、ソースコードはこちら

前回は、自作MIDI音源「CureSynth」のソフトウェア概要について紹介しました。
今回は、STM32F7+HALライブラリを用い、MIDIメッセージを「USART割り込み」で受信する方法と、受信したMIDIメッセージを蓄積するためのリングバッファについて紹介します。

1.MIDI受信回路

本稿で対象とする回路は、以前作成したvs1053b用の回路を転用し、USART3(RX)に直結しています。以下、USART3をMIDI受信用として話を進めます。

STM32F7VITxのUSART3_RXにMIDI受信回路を接続した図

2.USART割り込みによる受信

2.1.ペリフェラルの設定

USART3をMIDI受信用に設定します。STM32ではペリフェラルの設定がややこしいので、STM32CubeMXを使用すると便利だと思います。説明するのにも、設定画面を載せるのが手っ取り早いですね。たまにバグがあるので、自動生成されたソースコードはよく眺める必要はありますが…

USART3のMIDI受信用の設定 1

STM32CubeMXで、USART3をAsynchronous(非同期)に設定します。フロー制御は行わないためオフとします。

 

USART3のMIDI受信用の設定 2

ConfigurationタブのUSART3ボタンを押し、Parameter Settingタブの内容を設定します。通信速度を31.25kbps、8bitパリティなし、ストップビットを1(H)にします。MIDIはLSB firstなので、MSB FirstをDisableにしておきます。

MIDIのハードウェアレベルの仕様については、以下の記事がとても読みやすいので、お勧めします。
MIDI のハードウェアについて(Y-Lab. Electronics, Elekenさん)

余談ですが、STM32のUSARTは、RX/TXの入れ替えや論理の入れ替えなどをハード側でやってくれるので、安心して設計ミスができますね!

 

USART3のMIDI受信用の設定 3

さらに、NVIC Settingsで、USART3割り込みを有効にしておき、割り込みが発生できるようにします。

以上で設定は完了です。

2.2.実装

USART割り込みのサンプルです。

製作記事その3で示したとおり、MIDIメッセージはバイト単位なので、1バイト(8bit)受信するごとに割り込みが発生するようにします。

また、割り込み処理内でリングバッファ(FIFO)に蓄積し、処理の安定化を図ります。関数cureMidiBufferEnqueue()はバッファへ蓄積する関数です。後述します。

/*main.c*/

//グローバル変数
uint8_t midi_recieved_buf;

//1バイト受信するとコールされる割り込み関数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART3)
	{
		//受信内容をリングバッファに蓄積する
		cureMidiBufferEnqueue(&midi_recieved_buf);

		//次の受信に備える
		HAL_UART_Receive_IT(&huart3, &midi_recieved_buf,1);
	}
}

int main(void)
{
	//(中略)
	
	//MIDIメッセージの受信を開始。1バイト受信すると、割り込みを発生させる。
	HAL_UART_Receive_IT(&huart3, &midi_recieved_buf, 1);
	
	//(中略)
}

3.リングバッファ

3.1.概念

リングバッファ(循環バッファ)について、MIDIメッセージの受信に必要な概念に限定して説明します。汎用的な内容ついては、以下の記事が分かりやすいと思います。
循環バッファ( ++C++; // 未確認飛行 C , 岩永 信之さん)
キュー(お気楽C言語プログラミング超入門, 広井 誠さん)

 

製作記事その3で示したとおり、MIDIメッセージの受信はUSART割り込みで行い、MIDIメッセージの解析はメインループ内で行うため、MIDIメッセージ受信と解析のタイミングが同期するとは限りません。そこで受信したMIDIメッセージをバッファに蓄積しておき、解析するタイミングでバッファからデータを取り出します。そのためには、バッファにデータを蓄積した順に取り出す必要があります。

これを実現するのがリングバッファです。リングバッファは配列構造をリング状に(論理的に)接続したもので、イメージは次の通りです。リング状にすることで、データ領域の先頭・終端を任意の位置にすることができるため、使用済みの領域を再利用できます。エコですね。

uint8_t型、要素数nのリングバッファ

 

バッファの蓄積(Enqueue)/取り出し(Dequeue)のイメージ図を下記に示します。この例では、(a)初期状態に対し、(b)[0x90, 0x3C, 0x64]の順に3バイト蓄積、(c)[0x90]の1バイト取りだし、(d)[0x91, 0x3D, 0x65]の順に3バイト蓄積、と操作しています。

リングバッファの操作:初期状態(a)→3バイト蓄積(b)→1バイト取り出し(c)→3バイト蓄積(d)

ところで、図中のfront, rearは、要素の先頭や終端にアクセスするためのインデックス変数です。図では、(a)~(d)の各処理直後の値を表しており、次のように使います。

  • front==rearのとき、バッファが空だと判断。(→上図(a))
  • frontがrearの一つ前であるとき、バッファが満杯だと判断。
  • 次に蓄積する位置は、frontとする。buffer[front]でアクセス。
  • 次に取り出すべき位置は、rearとする。buffer[rear]でアクセス。

このとき、front, rearの値を次のように操作すれば、バッファが適切に動作します。

  • バッファの蓄積(Enqueue)と同時にfrontを1増加
  • バッファの取り出し(Dequeue)と同時にrearを1増加
  • front, rearが要素数nを超えたら0にリセットし、要素数nで循環させる

3.2.実装

まず、バッファの構造体型を定義します。


/*curebuffer.h*/

//uint8_t用リングバッファ構造体
typedef struct{
	uint16_t idx_front;
	uint16_t idx_rear;
	uint16_t length;//バッファ長
	uint8_t  *buffer;//バッファの先頭アドレス
}RingBufferU8;

一般的に使えるように、バッファを配列としてではなくポインタ(*buffer)として確保しています。後述する初期化処理内で、領域を確保してから使います。
バッファ長はsizeof(buffer)で求まりますが、sizeofによる演算コスト増加を避けるため、初期化時にlengthに代入しておきます。

次に、このRingBufferU8構造体に対して、初期化処理・解放処理・データ蓄積処理・データ取り出し処理の関数を用意します。これらバッファ操作用関数は、curebuffer.h/cにまとめておきます。

/*curebuffer.c*/

//初期化処理
BUFFER_STATUS cureRingBufferU8Init(RingBufferU8 *rbuf, uint16_t buflen)
{
	uint32_t i;

	cureRingBufferU8Free(rbuf);

	rbuf->buffer = (uint8_t *)malloc( buflen * sizeof(uint8_t) );
	
	if(NULL == rbuf->buffer){
		return BUFFER_FAILURE;
	}
	
	for(i=0; i<buflen; i++){
		rbuf->buffer[i] = 0;
	}

	rbuf->length = buflen;

	return BUFFER_SUCCESS;
}

//解放処理
BUFFER_STATUS cureRingBufferU8Free(RingBufferU8 *rbuf)
{
	if(NULL != rbuf->buffer){
		free(rbuf->buffer);
	}

	rbuf->idx_front = rbuf->idx_rear = 0;
	rbuf->length = 0;

	return BUFFER_SUCCESS;
}

//データ蓄積処理
BUFFER_STATUS cureRingBufferU8Enqueue(RingBufferU8 *rbuf, uint8_t *inputc)
{
	if( ((rbuf->idx_front +1)&(rbuf->length -1)) == rbuf->idx_rear ){//バッファが満杯
		return BUFFER_FAILURE;
	}else{
		rbuf->buffer[rbuf->idx_front]=  *inputc;
		rbuf->idx_front++;
		rbuf->idx_front &= (rbuf->length -1);
		return BUFFER_SUCCESS;
	}
}

//データ取り出し処理
BUFFER_STATUS cureRingBufferU8Dequeue(RingBufferU8 *rbuf, uint8_t *ret)
{
	if(rbuf->idx_front == rbuf->idx_rear){//バッファが空
		return BUFFER_FAILURE;
	}else{
		*ret = (rbuf->buffer[rbuf->idx_rear]);
		rbuf->idx_rear++;
		rbuf->idx_rear &= (rbuf->length -1);
		return BUFFER_SUCCESS;
	}
}

ちなみに、ハイライトした行の演算(front, rearを要素数の範囲で循環させる演算)には、ビット演算を使い高速化しています。例えば下記の2つは同じ結果になりますが、1つ目の方が速いです。ただし要素数を2のべき乗としなければなりません。


//ビット演算(速い)
idx_front &= (length -1);

//条件分岐(遅い)
if( idx_front >= length ){
	idx_front -= length;
}

//ビット演算の性質上、要素数lengthには「2のべき乗」という制限あり。

 

バッファ操作用関数は、MIDI操作用関数群であるcuremidi.h/c内でwrapして使っています。下記のように、cureMidiInit()関数内でバッファ領域を初期化しておき、2.2節のようにcureMidiBufferEnqueue()関数を呼び出すことで、バッファにデータを蓄積できます。


/*curemidi.h, curemidi.c*/

//バッファ長さ
#define MIDIBUFFER_LENGTH (1024)

//バッファ用構造体
RingBufferU8 rxbuf;

//初期化処理
FUNC_STATUS cureMidiInit()
{
	//(中略)

	if( BUFFER_FAILURE == cureRingBufferU8Init(&rxbuf, MIDIBUFFER_LENGTH) ){
		return FUNC_ERROR;
	}
	
	//(中略)
	
	return FUNC_SUCCESS;
}

//データ蓄積処理のラップ関数
BUFFER_STATUS cureMidiBufferEnqueue(uint8_t* inputc)
{
	return cureRingBufferU8Enqueue(&rxbuf, inputc);
}
//※関数マクロにした方が高速化できると思いますが、読みやすさ優先で…

//他のラップ関数は割愛

 

ちなみにBUFFER_STATUS, FUNC_STATUSは、それぞれバッファ用、一般関数用のエラーフラグであり、enumで定義しています。コーディングの見やすさのために用意しています。

typedef enum{
	BUFFER_FAILURE,BUFFER_SUCCESS
}BUFFER_STATUS;

typedef enum{
	FUNC_ERROR,FUNC_SUCCESS
}FUNC_STATUS;

 

今回はここまで。
次回は、MIDIメッセージの解析処理について紹介します。