-

4.0 / 5.0

いくつかサーボモータを購入していたのだけど、ようやく試した。持っているのは上記 SG90 と GWS03T/2BBMG/JR。どちらも動かしてみた。

これらはどちらも JR というタイプ (ピンアサインの種類)。種類はこのページの線のタイプついてを見た。

典型的なプロトコルとしては 20ms ごとに 1000μs〜2000μs (1500μs が中立) のパルスを送るというものらしい (可動角度60度)。

とりあえず試そうと、16bit PWM タイマーを使ってやってみた。

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define clear_bit(v, bit) v &= ~(1 << bit)
#define set_bit(v, bit)   v |=  (1 << bit)

#define INPUT_NOR PB0
#define INPUT_REV PB1

static inline void setup_io () {
	/**
	 * Data Direction Register: 0=input, 1=output
	 * 必要なポートだけインプットポートにする。
	 */
	DDRB  = 0b11111100;
	DDRC  = 0b11111111;
	DDRD  = 0b11111111;

	PORTB = 0b00000011;
	PORTC = 0b00000000;
	PORTD = 0b00000000;

	uint16_t TIMER1_MODE = 14;
	TCCR1A = (0<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (((TIMER1_MODE>>1)&1)<<WGM11) | ((TIMER1_MODE&1)<<WGM10);
	TCCR1B = (((TIMER1_MODE>>3)&1)<<WGM13) | (((TIMER1_MODE>>2)&1)<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10);
	ICR1   = 20000; // Top
	OCR1B  = 1500;
	TIMSK1 = (0<<OCIE1B) | (0<<OCIE1A) | (0<<TOIE1);

	sei();
}

static inline void set_position(uint16_t angle) {
	int base = 600;
	int max  = 2400;
	uint16_t n = ((uint32_t)angle * max) / 180 + base;
	if (n < base) {
		n = base;
	} else
	if (n > max) {
		n = max;
	}
	OCR1B = n;
}

int main(void) {
	setup_io();
	set_position(0);

	for (;;) {
		if (bit_is_clear(PINB, INPUT_NOR)) {
			set_position(0);
		}

		if (bit_is_clear(PINB, INPUT_REV)) {

			set_position(180);
		}

		_delay_ms(10);
	}
}

ハマったポイント

可動角度

ググると 1000μs〜2000μs のパルスというサイトばかりヒットするが、これは可動角度60度の場合で、実際は 600us〜2400μs までのパルスを受け付け、180度可動可能なものが多いみたい? よくわからないけど、手元の2種類についてはだいたいこの範囲のパルスを受けつけた。範囲外のパルスを入力すると「ジジジジ」とか「ブブブブ」とかいって止まり、消費電力が多くなる。

電圧

電圧が足りないと、指定した角度付近まで移動したあと「ジジジジ」と止まってしまうことがあった。

制御方法

16bit PWM だと1つの AVR で2つまでしか駆動できないので、割込みを使った別の方法を検討してもよさそう。

Arduino だと特に何も考えなくてもたくさんサーボを制御できるみたい。

  1. トップ
  2. tech
  3. RC サーボの制御

http://akizukidenshi.com/catalog/g/gM-08461/

最近「超小型USBシリアル変換モジュール」というのが秋月から出たみたいで、買ったのはいいけど、そのままだとちょっと使いにくいわけです。 ケースに組込むにしても MicroUSB だし取り付け用の穴もあいてないので難儀する。ブレッドボードで使うのがメインの用途なんでしょうけど…

なんかいいのないかと思いましたが、タカチ SW-20 (15x15x20) を買ってみたら完全にピッタリだったのでオススメです。ケースの蓋についている溝にピッタリハマってくれるサイズなので、ケースを少し削るだけで MicroUSB にアクセスでき、適当にエポキシで固めてしまえば便利。一応電源LED用の穴もあけたけど、なくてもよさそう。

  1. トップ
  2. tech
  3. 秋月の小型 USB Serial 基板とタカチの小型ケースがぴったり

KX3 と PC だけで SWR グラフにできたら便利なのになー と思っていたけど、少し前に調べたところ SWR を直接読む方法がなく、諦めていた。

しかし、KX3 Utility で Power Calibration を実行すると、SWR を読んでいるっぽい挙動が確認でき、読める方法があるということがわかった。KX3 Utility は親切にも全部のコマンドを出力してくれてるので、それを読んだだけでうまいこといけた。

やりかたとしては

  • TUNE ボタンを押す (SWH16; スイッチホールド)
  • ディスプレイに現在の SWR が表示される
  • DS; で「ディスプレイに表示されている内容」を取得する
  • SWR とれる

という感じだった。いろいろとコードを追加して、現在周波数の±を指定してSWRを取得するコードを書いてみた。結構いい感じ

#!ruby

require 'serialport'

@port = SerialPort.new(
	"/dev/tty.usbserial-A402PY11",
	38400,
	8,
	1,
	0
)
@port.set_encoding(Encoding::BINARY)

TEXT_MAP = {
	'<' => 'L',
	'>' => '-',
	'@' => ' ',
	'K' => 'H',
	'M' => 'N',
	'Q' => 'O',
	'V' => 'U',
	'W' => 'I',
	'X' => 'c-bar',
	'Z' => 'c',
	'[' => 'r-bar',
	'\\' => 'λ',
	']' => '',
	'^' => '',
}

def get_swr(step=3)
	ret = 0
	step.times do 
		@port << "DS;"
		data = @port.gets(";")
		t, a, f = *data.match(/DS(........)(.)(.)/).captures
		t = t.split(//).reduce("") {|r, i|
			chr = (i.ord & 0b01111111).chr
			r << ( ( (i.ord & 1<<7) != 0) ? ("." + chr) : (chr) )
		}.gsub(Regexp.new(TEXT_MAP.keys.map {|i| Regexp.escape(i) }.join("|")), TEXT_MAP)
		# KX3 displays SWR to main display as following : "1.2-1"
		if m = t.match(/([\d]+.[\d]+)-I/)
			swr = m[1]
		else
			return nil
		end
		ret += swr.to_f
		# p [t, a, f, swr]
	end
	ret / step
end

def get_freq
	@port << "FA;"
	res = @port.gets(";")
	freq = res.match(/FA(\d{11})/)[1].to_i
end

def set_freq(freq)
	@port << ("FA%011d;" % freq)
	get_freq == freq or raise "Failed to set freq"
end

def bypass_atu(&block)
	@port << "MN023;"
	@port << "MP;"
	current = @port.gets(";")
	current =~ /^MP\d{3}/ or raise "Unknown Responde #{current}"
	@port << "MP001;MP;"
	res = @port.gets(";")
	res == "MP001;" or raise "Failed to set KAT3 bypass: #{res}"
	@port << "MN255;"
	block.call
ensure
	@port << "MN023;"
	@port << current
	@port << "MN255;"
end

def tune(&block)
	# Switch Hold Emulation 16 = TUNE
	@port << 'SWH16;'
	sleep 0.5
	@port << 'TQ;'
	@port.gets(";") == 'TQ1;' or raise "Band END"
	sleep 0.3
	block.call
ensure
	@port << "RX;"
	sleep 0.5
	@port << "TQ;"
	@port.gets(";") == 'TQ0;' or raise "Failed to back to RX"
end

def scan_swr
	result = []
	@port << "PC;"
	current_power = @port.gets(";")
	@port << "PC001;PC;"
	@port.gets(";") == "PC001;" or raise "Failed to set power"
	bypass_atu do
		freq = get_freq
		step = 20e3
		range = Range.new(freq - 100e3, freq + 100e3)
		
		catch(:done) do
			begin
				set_freq(range.first)
				tune do
					range.step(step) {|n|
						set_freq(n)
						swr = get_swr(3)
						if swr.nil?
							throw :done
						end
						r = [n, swr]
						p r
						result << r
					}
				end
			rescue => e
				if e.message = "Band END"
					range = Range.new(range.first + step, range.last)
					retry
				end
			ensure
				set_freq(freq)
			end
		end
	end
	result
ensure
	@port << current_power
	@port << "PC;"
	@port.gets(";") == current_power or raise "Failed to back to power"
end

p scan_swr
  1. トップ
  2. tech
  3. KX3 でシリアルポートから SWR を読み出す

 -

2.0 / 5.0

WLI-UC-GNM を使ってたんだけど、24時間つけっぱなしのサーバみたいな用途だと、過熱しすぎて止まってしまうことが多々あり、ぶっちゃけ使いもんにならんよ! という感じだった。つけっぱにしないなら使えるんだけど……

ので、GW-USNANO2A を買ってみた。

 -

3.0 / 5.0

が、めちゃくちゃ接続が切れまくる。

ping を打ち続ければ大丈夫なので、またパワーマネジメント系の問題か…と思ったが、iwconfig の結果は Power Management:off となっており、よくわからなかった。

調べてみると、このドライバに関してはカーネルモジュールに対するオプションによってパワーマネジメントを決めるらしい……

現在の状態は sysfs を通し

$ cat /sys/module/8192cu/parameters/rtw_power_mgnt 
1

で確認できる。1 だとパワーマネジメントが効いている。

$ sudo su
# echo 'options 8192cu rtw_power_mgnt=0' > /etc/modprobe.d/8192cu.conf
# reboot

すると、パワーマネジメントを切って起動させることができる。

$ cat /sys/module/8192cu/parameters/rtw_power_mgnt 
0

これで切れることがなくなった。

  1. トップ
  2. tech
  3. RaspberyPi 用の WiFi アダプタ探し。または GW-USNANO2A が切れまくるのをなおす