bitwizard 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/bitwizardctl +327 -0
- data/lib/bitwizard.rb +219 -0
- data/lib/bitwizard/motor.rb +155 -0
- data/lib/bitwizard/nfets.rb +186 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a2f5266eca9ddb2e732db8ff87c674881adf6f1a
|
4
|
+
data.tar.gz: 1646160e172499d225f23b04519bb8d12b6d425d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c965fdbf80b846d6e6e2a22bd1861cf0ac66303148214e4c72a096bcce55ab54d533f5b31434cdc2bbc2c0375366d1b830b07b3b45f7d94b53f4aceb6024cac5
|
7
|
+
data.tar.gz: 2e97dd74df33ead09a1d70f5bf49ceeb35643c14f76d06b898cc1673e35a4328e40960b30c7fe0c6948297508293c19076d7fb0e588a966c245deae09e5767e5
|
data/bin/bitwizardctl
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bitwizard'
|
4
|
+
require 'optparse'
|
5
|
+
require 'readline'
|
6
|
+
|
7
|
+
class BasicLogger
|
8
|
+
def debug(string)
|
9
|
+
puts string
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Options = {
|
14
|
+
:bus => :spi,
|
15
|
+
:verbose => false,
|
16
|
+
:scan => true
|
17
|
+
}
|
18
|
+
|
19
|
+
class BitWizardCTL
|
20
|
+
|
21
|
+
DetailedHelp = {
|
22
|
+
:scan => { :text => "Scans for available boards" },
|
23
|
+
:list => { :text => "Lists all the found boards" },
|
24
|
+
:eeprom => { :args => "<addr>", :text => "Read the eeprom from the specified address(es), or all found boards if none given" },
|
25
|
+
:exit => { :text => "Exits the program" },
|
26
|
+
:help => { :text => "Lists all the valid commands, or detailed help for a given command" },
|
27
|
+
:pwm => { :args => "<addr> [port [value/'enable'/'disable']]", :text => "Get/Set the PWM value for a certain port on a board" },
|
28
|
+
:stepper => { :args => "<addr> <'current'/'target'/'delay'> [value]", :text => "Get/Set a value for a stepper motor on the specified board" },
|
29
|
+
:motor => { :args => "<addr> <'A'/'B'> <speed/'stop'>", :text => "Change speed of a motor on the specified board" }
|
30
|
+
}
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@defaultlogger = BasicLogger.new if Options[:verbose]
|
34
|
+
@boards = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def run!
|
38
|
+
if Options[:scan] then
|
39
|
+
cmd_scan
|
40
|
+
puts
|
41
|
+
end
|
42
|
+
|
43
|
+
complete = Proc.new { |line|
|
44
|
+
ret = []
|
45
|
+
DetailedHelp.each do |cmd, help|
|
46
|
+
data = line.split
|
47
|
+
data << "" if line.end_with? " "
|
48
|
+
|
49
|
+
ret << cmd.to_s unless data
|
50
|
+
next unless data
|
51
|
+
|
52
|
+
if data.length > 1 then
|
53
|
+
next unless cmd.to_s == data[0]
|
54
|
+
break if cmd.to_s == data[0] and not help.has_key? :args
|
55
|
+
|
56
|
+
arg_data = help[:args].split[data.length - 2]
|
57
|
+
|
58
|
+
if arg_data == "<addr>" then
|
59
|
+
#p @boards
|
60
|
+
@boards.each do |addr,_|
|
61
|
+
straddr = "0x#{addr.to_s(16)}"
|
62
|
+
ret << "#{straddr}" if /^#{Regexp.escape(data[1])}/ =~ straddr
|
63
|
+
end
|
64
|
+
elsif cmd == :stepper and data.length == 3 then
|
65
|
+
["current","target","delay"].each do |str|
|
66
|
+
ret << "#{str}" if /^#{Regexp.escape(data[2])}/ =~ str
|
67
|
+
end
|
68
|
+
elsif cmd == :motor then
|
69
|
+
if data.length == 3 then
|
70
|
+
["A","B"].each do |str|
|
71
|
+
ret << "#{str}" if data[2].length == 0 or data[2] == str
|
72
|
+
end
|
73
|
+
elsif data.length == 4 and data[3].length > 0 then
|
74
|
+
ret << "stop" if "stop".start_with? data[3]
|
75
|
+
end
|
76
|
+
elsif cmd == :pwm and data.length == 4 then
|
77
|
+
["enable", "disable"].each do |str|
|
78
|
+
ret << "#{str}" if str.start_with? data[3]
|
79
|
+
end if data[3].length > 0
|
80
|
+
end
|
81
|
+
|
82
|
+
if ret.length == 1 then
|
83
|
+
ret = [ data[0..-2].join(" ") + " " + ret[0] ]
|
84
|
+
end
|
85
|
+
else
|
86
|
+
ret << cmd.to_s if /^#{Regexp.escape(line)}/ =~ cmd.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ret
|
91
|
+
}
|
92
|
+
|
93
|
+
Readline.completer_word_break_characters = ""
|
94
|
+
Readline.completion_append_character = " "
|
95
|
+
Readline.completion_proc = complete
|
96
|
+
|
97
|
+
while line = Readline.readline("[BitWizard]\# ", true) do
|
98
|
+
run_cmd!(*line.split)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def run_cmd!(*args)
|
103
|
+
command = args.shift
|
104
|
+
if respond_to? "cmd_#{command}".to_sym, true then
|
105
|
+
begin
|
106
|
+
send("cmd_#{command}".to_sym, *args)
|
107
|
+
rescue ArgumentError => ex
|
108
|
+
puts " \e[31m#{ex}\e[0m"
|
109
|
+
if Options[:verbose] then
|
110
|
+
ex.backtrace.each do |line|
|
111
|
+
puts " #{line}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
puts "Unknown command '#{command}', try 'help' for a list of valid ones"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def parse_addr(addr)
|
123
|
+
addr = eval addr if /^0x[0-9a-fA-F]{1,2}$/ =~ addr
|
124
|
+
addr = addr.to_i unless addr.is_a? Fixnum
|
125
|
+
|
126
|
+
raise ArgumentError.new "Address must be between 0x00 and 0xff" unless (0..255).include? addr
|
127
|
+
addr
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_board(addr)
|
131
|
+
board = @boards[addr] if @boards.has_key? addr
|
132
|
+
unless board then
|
133
|
+
board = BitWizard::Board.detect :address => addr, :bus => Options[:bus], :logger => (@defaultlogger if Options[:verbose])
|
134
|
+
@boards[addr] = board if board.valid?
|
135
|
+
end
|
136
|
+
|
137
|
+
raise ArgumentError.new "No valid board on 0x#{addr.to_s(16)}" unless board.valid?
|
138
|
+
board
|
139
|
+
end
|
140
|
+
|
141
|
+
def cmd_help(*)
|
142
|
+
longestcmd = 0
|
143
|
+
DetailedHelp.each do |cmd,data|
|
144
|
+
curlen = cmd.length
|
145
|
+
curlen += data[:args].length if data.has_key? :args
|
146
|
+
longestcmd = curlen if curlen > longestcmd
|
147
|
+
end
|
148
|
+
|
149
|
+
puts "Available commands:"
|
150
|
+
DetailedHelp.each do |cmd, help|
|
151
|
+
puts " #{cmd} #{help[:args] if help.has_key? :args} #{" " * (longestcmd - cmd.length - (help[:args] and help[:args].length or 0))}#{help[:text]}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def cmd_exit(*)
|
156
|
+
exit
|
157
|
+
end
|
158
|
+
|
159
|
+
def cmd_scan(*)
|
160
|
+
puts "Scanning for boards..."
|
161
|
+
|
162
|
+
@boards = { }
|
163
|
+
(0..0xff).step(2) do |address|
|
164
|
+
begin
|
165
|
+
temp = BitWizard::Board.detect :address => address, :bus => Options[:bus], :logger => (@defaultlogger if Options[:verbose])
|
166
|
+
next unless temp.valid?
|
167
|
+
|
168
|
+
@boards[temp.address] = temp
|
169
|
+
puts "0x#{address.to_s(16)}: #{temp.type} (#{temp.version})"
|
170
|
+
rescue ArgumentError
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def cmd_list(*)
|
176
|
+
@boards.each do |_, board|
|
177
|
+
puts "0x#{board.address.to_s(16)}: #{board.type} (#{board.version})"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def cmd_eeprom(*args)
|
182
|
+
if args.count == 0 then
|
183
|
+
@boards.each do |_, board|
|
184
|
+
puts "0x#{board.address.to_s(16)}: #{board.read(0x02, 4).pack("C*").unpack("l>")[0]}"
|
185
|
+
end
|
186
|
+
elsif args.count == 1 then
|
187
|
+
begin
|
188
|
+
addr = parse_addr args[0]
|
189
|
+
board = get_board addr
|
190
|
+
puts "0x#{addr.to_s(16)}: #{board.read(0x02, 4).pack("C*").unpack("l>")[0]}"
|
191
|
+
rescue ArgumentError => err
|
192
|
+
puts "0x#{addr.to_s(16)}: #{err}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def cmd_pwm(*args)
|
198
|
+
raise ArgumentError.new "Wrong number of arguments (#{args.count} for 1-3)" unless (1..3).include? args.count
|
199
|
+
addr, port, value = *args if args.count == 3
|
200
|
+
value = value.to_i if not ["enable","disable"].include? value
|
201
|
+
addr, port = *args if args.count == 2
|
202
|
+
port = port.to_i if port
|
203
|
+
addr = args[0] if args.count == 1
|
204
|
+
|
205
|
+
raise ArgumentError.new "Value must be between 0 and 255" unless value.is_a? String or (0..255).include? value
|
206
|
+
|
207
|
+
addr = parse_addr addr
|
208
|
+
board = get_board addr
|
209
|
+
|
210
|
+
raise ArgumentError.new "Board doesn't support PWM actions" unless board.known_board[:features].include? :pwm
|
211
|
+
|
212
|
+
puts "0x#{addr.to_s(16)}:"
|
213
|
+
|
214
|
+
if port then
|
215
|
+
if value.is_a? String then
|
216
|
+
succ = board.pwm_disable port if value == "disable"
|
217
|
+
succ = board.pwm_enable port if value == "enable"
|
218
|
+
puts " PWM \##{port}: #{value == "off" ? "disabled" : "enabled"}." if succ
|
219
|
+
pust " Failed to change PWM status on port \##{port}" unless succ
|
220
|
+
else
|
221
|
+
ports = board.pwm_ports
|
222
|
+
unless ports.include? port then
|
223
|
+
puts " PWM not enabled on port #{port}"
|
224
|
+
return
|
225
|
+
end
|
226
|
+
|
227
|
+
board[port] = value if value
|
228
|
+
puts " PWM \##{port}: #{((board[port] / 255.0) * 100).round(2)}% (#{board[port]})"
|
229
|
+
end
|
230
|
+
else
|
231
|
+
board.pwm_ports.each do |port|
|
232
|
+
puts " PWM \##{port}: #{((board[port] / 255.0) * 100).round(2)}% (#{board[port]})"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def cmd_stepper(*args)
|
238
|
+
raise ArgumentError.new "Wrong number of arguments (#{args.count} for 2-3)" unless (2..3).include? args.count
|
239
|
+
|
240
|
+
addr, type, value = *args if args.count == 3
|
241
|
+
value = value.to_i if value
|
242
|
+
addr, type = *args if args.count == 2
|
243
|
+
type = type.to_sym
|
244
|
+
|
245
|
+
raise ArgumentError.new "Value to #{args.count == 3 ? "set" : "read"} must be 'current', 'target', or 'delay'" unless [:current, :target, :delay].include? type
|
246
|
+
|
247
|
+
addr = parse_addr addr
|
248
|
+
board = get_board addr
|
249
|
+
|
250
|
+
raise ArgumentError.new "Board doesn't support stepper actions" unless board.known_board[:features].include? :stepper
|
251
|
+
|
252
|
+
case type
|
253
|
+
when :current
|
254
|
+
board.stepper_position = value if value
|
255
|
+
puts "0x#{addr.to_s(16)}'s current position: #{board.stepper_position}"
|
256
|
+
when :target
|
257
|
+
board.stepper_target = value if value
|
258
|
+
puts "0x#{addr.to_s(16)}'s target position: #{board.stepper_target}"
|
259
|
+
when :delay
|
260
|
+
board.stepper_delay = value if value
|
261
|
+
puts "0x#{addr.to_s(16)}'s step delay: #{board.stepper_delay / 10.0}ms (#{board.stepper_delay})"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def cmd_motor(*args)
|
266
|
+
raise ArgumentError.new "Wrong number of arguments (#{args.count} for 3)" unless args.count == 3
|
267
|
+
|
268
|
+
addr, motor, value = *args
|
269
|
+
motor = motor.to_sym
|
270
|
+
value = value.to_i unless value == "stop"
|
271
|
+
|
272
|
+
raise ArgumentError.new "Motor must be 'A' or 'B'" unless [:A,:B].include? motor
|
273
|
+
raise ArgumentError.new "Value must be between -255 - 255" unless value.is_a? String or (-255..255).include? value
|
274
|
+
|
275
|
+
addr = parse_addr addr
|
276
|
+
board = get_board addr
|
277
|
+
|
278
|
+
raise ArgumentError.new "Board doesn't support motor actions" unless board.known_board[:features].include? :motor
|
279
|
+
|
280
|
+
if value.is_a? String
|
281
|
+
board.motor_stop motor
|
282
|
+
else
|
283
|
+
board.motor_spin motor, value
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
OptionParser.new do |opts|
|
290
|
+
opts.banner = "Usage: #{File.basename($0)} [options] [command [arguments]]"
|
291
|
+
opts.separator ""
|
292
|
+
opts.separator "Options:"
|
293
|
+
|
294
|
+
opts.on("-i", "--i2c", "Use I2C instead of SPI") do |i|
|
295
|
+
Options[:bus] = :i2c
|
296
|
+
end
|
297
|
+
|
298
|
+
opts.on("-s", "--skip", "Skip the scan on start") do
|
299
|
+
Options[:scan] = false
|
300
|
+
end
|
301
|
+
|
302
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
303
|
+
Options[:verbose] = v
|
304
|
+
end
|
305
|
+
|
306
|
+
opts.on("-h", "--help", "Shows this text") do
|
307
|
+
puts opts
|
308
|
+
exit
|
309
|
+
end
|
310
|
+
|
311
|
+
opts.separator ""
|
312
|
+
opts.separator "Commands:"
|
313
|
+
|
314
|
+
BitWizardCTL::DetailedHelp.each do |cmd, help|
|
315
|
+
opts.separator " #{cmd}#{help.has_key? :args and " #{help[:args]}" or ""}"
|
316
|
+
opts.separator " #{help[:text]}"
|
317
|
+
|
318
|
+
end
|
319
|
+
end.parse!
|
320
|
+
|
321
|
+
ctl = BitWizardCTL.new
|
322
|
+
|
323
|
+
if ARGV.count > 0 then
|
324
|
+
ctl.run_cmd!(*ARGV)
|
325
|
+
else
|
326
|
+
ctl.run!
|
327
|
+
end
|
data/lib/bitwizard.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'pi_piper'
|
2
|
+
|
3
|
+
module BitWizard
|
4
|
+
|
5
|
+
class Board
|
6
|
+
|
7
|
+
#Detects the type of board on the given address and creates the correct handler class for it.
|
8
|
+
#
|
9
|
+
# @param [Number] address The address to check.
|
10
|
+
# @param [optional, Hash] options A Hash of options.
|
11
|
+
# @option options [Symbol] :bus The type of bus the board is connected on. (:spi or :i2c)
|
12
|
+
# @option options [Logger] :logger A logger you want to attach to the board.
|
13
|
+
def Board.detect(options)
|
14
|
+
options = {
|
15
|
+
:address => -1,
|
16
|
+
:bus => :spi,
|
17
|
+
:logger => NullLogger.new
|
18
|
+
}.merge(options).merge({
|
19
|
+
:type => :auto_detect,
|
20
|
+
:skip_check => false
|
21
|
+
})
|
22
|
+
|
23
|
+
options[:logger] = NullLogger.new unless options[:logger]
|
24
|
+
|
25
|
+
temp = BitWizard::Board.new options
|
26
|
+
correct = temp.known_board[:constructor].call(options.merge({:skip_check => true})) if temp.valid?
|
27
|
+
|
28
|
+
correct.instance_variable_set(:@type, temp.type)
|
29
|
+
correct.instance_variable_set(:@version, temp.version)
|
30
|
+
correct.instance_variable_set(:@known_board, temp.known_board)
|
31
|
+
|
32
|
+
correct
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :type, :version, :address, :bus, :known_board
|
36
|
+
attr_accessor :logger
|
37
|
+
|
38
|
+
#Creates a generic board handle for reading and writing directly.
|
39
|
+
#
|
40
|
+
# @param [Hash] options A hash of options.
|
41
|
+
# @option options [Fixnum] :address The address of the board. (0x00..0xff)
|
42
|
+
# @option options [Symbol] :type The board type, defaults to auto detecting. (identifier)
|
43
|
+
# @option options [Symbol] :bus The bus it's connected to. (:spi or :i2c)
|
44
|
+
# @option options [Boolean] :skip_check Skip the self check that runs on creation.
|
45
|
+
# @option options [Logger] :logger Add a logger here to log data that's sent and received.
|
46
|
+
def initialize(options)
|
47
|
+
options = {
|
48
|
+
:address => -1,
|
49
|
+
:type => :auto_detect,
|
50
|
+
:bus => :spi,
|
51
|
+
:skip_check => false,
|
52
|
+
:logger => NullLogger.new
|
53
|
+
}.merge(options)
|
54
|
+
|
55
|
+
raise ArgumentError.new "Bus must be :spi or :i2c." unless options[:bus] == :spi or options[:bus] == :i2c
|
56
|
+
|
57
|
+
@logger = options[:logger]
|
58
|
+
@address = options[:address]
|
59
|
+
@type = options[:type]
|
60
|
+
@bus = options[:bus]
|
61
|
+
|
62
|
+
self_check! unless options[:skip_check]
|
63
|
+
end
|
64
|
+
|
65
|
+
#Returns if the board has a valid communication address
|
66
|
+
def valid?
|
67
|
+
return false if @address == -1 or @type == :auto_detect
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
#Writes a value to the board, either a single byte or several in the form of a string
|
72
|
+
#
|
73
|
+
# @param [Number] reg The registry address to write to
|
74
|
+
# @param [Number|String] value The data to write to the board
|
75
|
+
def write(reg, value)
|
76
|
+
raise ArgumentError.new "#{reg} is not a valid register, must be a number between 0x00..0xff" unless reg.is_a? Fixnum and (0..255).include? reg
|
77
|
+
raise ArgumentError.new "#{value} is not a valid value, must be a single byte or a string" unless (value.is_a? Fixnum and (0..255).include? value) or (value.is_a? String)
|
78
|
+
|
79
|
+
value = value.unpack("C*") if value.is_a? String
|
80
|
+
return spi_write(reg, value) if @bus == :spi
|
81
|
+
return i2c_write(reg, value) if @bus == :i2c
|
82
|
+
end
|
83
|
+
|
84
|
+
#Reads a value from the board
|
85
|
+
#
|
86
|
+
# @param [Number] reg The registry address to read from
|
87
|
+
# @param [Number] count The number of bytes to read
|
88
|
+
def read(reg, count)
|
89
|
+
raise ArgumentError.new "#{reg} is not a valid register, must be a number between 0x00..0xff" unless reg.is_a? Fixnum and (0..255).include? reg
|
90
|
+
|
91
|
+
return spi_read(reg, count) if @bus == :spi
|
92
|
+
return i2c_read(reg, count) if @bus == :i2c
|
93
|
+
end
|
94
|
+
|
95
|
+
#Changes the boards address
|
96
|
+
#
|
97
|
+
# The new address needs to follow these criteria;
|
98
|
+
# * Must be a 8-bit number between 0x00 and 0xff
|
99
|
+
# * Must not have it's least significant bit set
|
100
|
+
#
|
101
|
+
# @param [Number] new_address The new address of the board
|
102
|
+
def address=(new_address)
|
103
|
+
raise ArgumentError.new "#{new_address} is not a valid address" unless new_address.is_a? Fixnum and (0..255).include? new_address and new_address|1 != new_address
|
104
|
+
|
105
|
+
old_address = @address
|
106
|
+
@address = new_address
|
107
|
+
identifier = read(0x01, 20).pack("C*").split("\0")[0]
|
108
|
+
@address = old_address
|
109
|
+
|
110
|
+
Known_Boards.each do |name, data|
|
111
|
+
if name =~ identifier then
|
112
|
+
raise ArgumentError.new "Another board (#{identifier}) already exists on #{new_address}!"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
write 0xf1, 0x55
|
117
|
+
write 0xf2, 0xaa
|
118
|
+
write 0xf0, new_address
|
119
|
+
|
120
|
+
@address = new_address
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
class NullLogger
|
126
|
+
def debug(*) end
|
127
|
+
end
|
128
|
+
|
129
|
+
#Performs a self check of the board
|
130
|
+
#
|
131
|
+
# This includes contacting the board and checking that it's of the correct type.
|
132
|
+
def self_check!
|
133
|
+
found_board = nil
|
134
|
+
Known_Boards.each do |name, data|
|
135
|
+
if name =~ @type then
|
136
|
+
@address = data[:default_address] unless (0..255).include? @address
|
137
|
+
found_board = {
|
138
|
+
:name => name,
|
139
|
+
:data => data
|
140
|
+
}
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
raise ArgumentError.new "Don't know what board '#{@type}' is." if not found_board and @type != :auto_detect
|
145
|
+
raise ArgumentError.new "Board type is 'auto_detect', but invalid address #{@address} given." if @type == :auto_detect and not (0..255).include? @address
|
146
|
+
|
147
|
+
identifier = read(0x01, 20).pack("C*").split("\0")[0]
|
148
|
+
raise ArgumentError.new "No response from board" if identifier.empty?
|
149
|
+
|
150
|
+
if @type == :auto_detect then
|
151
|
+
Known_Boards.each do |name, data|
|
152
|
+
if name =~ identifier then
|
153
|
+
@type, @version = *identifier.split
|
154
|
+
@type = @type.to_sym
|
155
|
+
@known_board = data
|
156
|
+
break
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
raise ArgumentError.new "No known board of type '#{identifier}'." if @type == :auto_detect and not identifier.empty?
|
161
|
+
else
|
162
|
+
Known_Boards.each do |name, data|
|
163
|
+
if name =~ identifier then
|
164
|
+
@version = identifier.split[1]
|
165
|
+
@known_board = data
|
166
|
+
raise ArgumentError.new "Board reports type #{real_name}, which does not match #{@type}" unless found_board[:data] == data
|
167
|
+
break
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
def spi_write(reg, value)
|
176
|
+
@logger.debug("SPI [0x#{@address.to_s(16)}] <-- 0x#{reg.to_s(16)}: #{value.is_a? Array and value.pack("C*").inspect or value.inspect}")
|
177
|
+
PiPiper::Spi.begin do |spi|
|
178
|
+
spi.write @address, reg, *value if value.is_a? Array
|
179
|
+
spi.write @address, reg, value unless value.is_a? Array
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def spi_read(reg, count)
|
184
|
+
data = PiPiper::Spi.begin do |spi|
|
185
|
+
spi.write @address | 1, reg, *Array.new(count, 0)
|
186
|
+
end[2..-1]
|
187
|
+
@logger.debug("SPI [0x#{@address.to_s(16)}] --> 0x#{reg.to_s(16)}: #{data.pack("C*").inspect}")
|
188
|
+
data
|
189
|
+
end
|
190
|
+
|
191
|
+
def i2c_write(reg, value)
|
192
|
+
@logger.debug("I2C [0x#{@address.to_s(16)}] <-- 0x#{reg.to_s(16)}: #{value.is_a? Array and value.pack("C*").inspect or value.inspect}")
|
193
|
+
PiPiper::I2C.begin do |i2c|
|
194
|
+
data = [reg]
|
195
|
+
data << value unless value.is_a? Array
|
196
|
+
data += value if value.is_a? Array
|
197
|
+
|
198
|
+
i2c.write({ :to => @address, :data => data })
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def i2c_read(reg, count)
|
203
|
+
data = PiPiper::I2C.begin do |i2c|
|
204
|
+
i2c.write({ :to => @address | 1, :data => [reg, *Array.new(count, 0)] })
|
205
|
+
i2c.read count
|
206
|
+
end
|
207
|
+
@logger.debug("I2C [0x#{@address.to_s(16)}] --> 0x#{reg.to_s(16)}: #{data.pack("C*").inspect}")
|
208
|
+
data
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
#A list of boards the library knows how to handle.
|
213
|
+
Known_Boards = {
|
214
|
+
|
215
|
+
} unless defined? Known_Boards
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
Dir[File.dirname(__FILE__) + "/bitwizard/*.rb"].each { |file| require file }
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module BitWizard
|
2
|
+
module Boards
|
3
|
+
|
4
|
+
class Motor < Board
|
5
|
+
|
6
|
+
#Create an instance of a Motor board
|
7
|
+
#
|
8
|
+
# @param [optional, Hash] options A Hash of options.
|
9
|
+
def initialize(options={})
|
10
|
+
options = {
|
11
|
+
:bus => :spi
|
12
|
+
}.merge(options)
|
13
|
+
options = options.merge({
|
14
|
+
:type => "#{options[:bus]}_motor".to_sym,
|
15
|
+
})
|
16
|
+
|
17
|
+
super(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
#Start spinning a motor on a specific port
|
21
|
+
#
|
22
|
+
# @param [Symbol] port The port to spin (:A or :B)
|
23
|
+
# @param [Number] value The direction and speed of the motor (-255..255)
|
24
|
+
def motor_start(port, value)
|
25
|
+
raise ArgumentError.new("Port must be :A or :B") unless port == :A or port == :B
|
26
|
+
raise ArgumentError.new("Value must be an integer beween -255 and 255") unless value.is_a? Fixnum and (-255..255).include? value
|
27
|
+
|
28
|
+
basereg = 0x20
|
29
|
+
basereg = 0x30 if port == :B
|
30
|
+
|
31
|
+
if value < 0 then
|
32
|
+
write(basereg+1, -value)
|
33
|
+
elsif value > 0 then
|
34
|
+
write(basereg+1, value)
|
35
|
+
else
|
36
|
+
write(basereg+2, 1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#Stop spinning a motor on a specific port
|
41
|
+
#
|
42
|
+
# This is the same as running start_motor with the value 0
|
43
|
+
#
|
44
|
+
# @param [Symbol] port The port to stop (:A or :B)
|
45
|
+
def motor_stop(port)
|
46
|
+
raise ArgumentError.new "Port must be :A or :B" unless port == :A or port == :B
|
47
|
+
|
48
|
+
motor_start(port, 0)
|
49
|
+
end
|
50
|
+
|
51
|
+
#Read the current position of the stepper motor
|
52
|
+
#
|
53
|
+
# @return [Number] The current stepper position
|
54
|
+
def stepper_position
|
55
|
+
read(0x40, 4).pack("C*").unpack("l>")[0]
|
56
|
+
end
|
57
|
+
#Set the current position of the stepper motor, without actually moving it
|
58
|
+
#
|
59
|
+
# @param [Number] position The new position of the stepper motor
|
60
|
+
def stepper_position=(position)
|
61
|
+
raise ArgumentError.new "Position must be an integer" unless position.is_a? Fixnum
|
62
|
+
|
63
|
+
write(0x40, [position].pack("l>"))
|
64
|
+
end
|
65
|
+
|
66
|
+
#Read the target position of the stepper motor
|
67
|
+
#
|
68
|
+
# @return [Number] The target position of the stepper
|
69
|
+
def stepper_target
|
70
|
+
read(0x41, 4).pack("C*").unpack("l>")[0]
|
71
|
+
end
|
72
|
+
#Set the target position of the stepper motor
|
73
|
+
#
|
74
|
+
# @param [Number] position The target position for the stepper motor
|
75
|
+
def stepper_target=(position)
|
76
|
+
raise ArgumentError.new "Position must be an integer" unless position.is_a? Fixnum
|
77
|
+
|
78
|
+
write(0x41, [position].pack("l>"))
|
79
|
+
end
|
80
|
+
|
81
|
+
#Read the step delay of the stepper motor
|
82
|
+
#
|
83
|
+
# @return [Number] The stepdelay in tenths of a millisecond
|
84
|
+
def stepper_delay
|
85
|
+
read(0x43, 1)[0]
|
86
|
+
end
|
87
|
+
#Set the step delay of the stepper motor
|
88
|
+
#
|
89
|
+
# @param [Number] delay The new stepdelay, in tenths of a millisecond (maximum 255 - 25ms between steps)
|
90
|
+
def stepper_delay=(delay)
|
91
|
+
raise ArgumentError.new "Delay must be an integer between 0 and 255" unless delay.is_a? Fixnum and (0..255).include? delay
|
92
|
+
|
93
|
+
write(0x43, delay)
|
94
|
+
end
|
95
|
+
|
96
|
+
#Enables Pulse Width Modulation on the specified port
|
97
|
+
#
|
98
|
+
# @param [Number|Array] port The port/ports to enable PWM on
|
99
|
+
def pwm_enable(*port)
|
100
|
+
false # Board doesn't support enabling/disabling PWM
|
101
|
+
end
|
102
|
+
|
103
|
+
#Disables Pulse Width Modulation on the specified port
|
104
|
+
#
|
105
|
+
# @param [Number|Array] port The port/ports to disable PWM on
|
106
|
+
def pwm_disable(*port)
|
107
|
+
false # Board doesn't support enabling/disabling PWM
|
108
|
+
end
|
109
|
+
|
110
|
+
#Returns the ports that have PWM enabled
|
111
|
+
#
|
112
|
+
# @return [Array] An array containing the port numbers with PWM enabled
|
113
|
+
def pwm_ports
|
114
|
+
[1, 2, 3, 4] # Board doesn't support enabling/disabling PWM
|
115
|
+
end
|
116
|
+
|
117
|
+
#Checks if a port has PWM enabled
|
118
|
+
#
|
119
|
+
# @param [Number] port The port to check
|
120
|
+
# @return [Boolean] Is the port enabled for PWM control
|
121
|
+
def pwm_enabled?(port)
|
122
|
+
pwm_ports.include? port
|
123
|
+
end
|
124
|
+
|
125
|
+
#Read a PWM value from a port
|
126
|
+
#
|
127
|
+
# @param [Symbol] port The port to read from (1..4)
|
128
|
+
# @return [Number] The PWM value on the port
|
129
|
+
def [](port)
|
130
|
+
raise ArgumentError.new "Port must be an integer between 1 and 4" unless port.is_a? Fixnum and (1..4).include? port
|
131
|
+
|
132
|
+
read(0x50+(port-1), 1)[0]
|
133
|
+
end
|
134
|
+
|
135
|
+
#Set a PWM value from a port
|
136
|
+
#
|
137
|
+
# @param [Number] port The port to set (1..4)
|
138
|
+
# @param [Number] value The PWM value to set on the port (0..255)
|
139
|
+
def []=(port, value)
|
140
|
+
raise ArgumentError.new "Port must be an integer between 1 and 4" unless port.is_a? Fixnum and (1..4).include? port
|
141
|
+
|
142
|
+
write(0x50+(port-1), value)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
Known_Boards[/(spi|i2c)_motor/] = {
|
150
|
+
:default_address => 0x90,
|
151
|
+
:constructor => Proc.new { |options| BitWizard::Boards::Motor.new options },
|
152
|
+
:features => [ :motor, :stepper, :pwm ]
|
153
|
+
}
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module BitWizard
|
2
|
+
module Boards
|
3
|
+
|
4
|
+
class FETs < Board
|
5
|
+
|
6
|
+
attr_reader :num_FETs
|
7
|
+
|
8
|
+
#Create an instance of a FET board
|
9
|
+
#
|
10
|
+
# @param [optional, Hash] options A Hash of options.
|
11
|
+
# @option options [Number] :num The number of FETs on the board (3 or 7)
|
12
|
+
def initialize(options={})
|
13
|
+
options = {
|
14
|
+
:num => 3,
|
15
|
+
:bus => :spi
|
16
|
+
}.merge(options)
|
17
|
+
options = options.merge({
|
18
|
+
:type => "#{options[:bus]}_#{options[:num]}fets".to_sym,
|
19
|
+
})
|
20
|
+
|
21
|
+
raise ArgumentError.new "Number of FETs must be 3 or 7" unless options[:num] == 3 or options[:num] == 7
|
22
|
+
|
23
|
+
super(options)
|
24
|
+
|
25
|
+
@num_FETs = options[:num]
|
26
|
+
end
|
27
|
+
|
28
|
+
#Enables Pulse Width Modulation on the specified port
|
29
|
+
#
|
30
|
+
# @param [Number|Array] port The port/ports to enable PWM on
|
31
|
+
def pwm_enable(*port)
|
32
|
+
case port.count
|
33
|
+
when 0
|
34
|
+
raise ArgumentError.new "wrong number of arguments"
|
35
|
+
when 1
|
36
|
+
port = port.first
|
37
|
+
end
|
38
|
+
|
39
|
+
if port.is_a? Array then
|
40
|
+
port.each do |port|
|
41
|
+
pwm_enable port
|
42
|
+
end
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
raise ArgumentError.new "Port must be an integer between 1 and #{@num_FETs}" unless port.is_a? Fixnum and (1..@num_FETs).include? port
|
46
|
+
|
47
|
+
port = 2**(port-1)
|
48
|
+
|
49
|
+
curPWM = read(0x5f, 1)[0]
|
50
|
+
tgtPWM = curPWM | port
|
51
|
+
write(0x5f, tgtPWM)
|
52
|
+
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
#Disables Pulse Width Modulation on the specified port
|
57
|
+
#
|
58
|
+
# @param [Number|Array] port The port/ports to disable PWM on
|
59
|
+
def pwm_disable(*port)
|
60
|
+
case port.count
|
61
|
+
when 0
|
62
|
+
raise ArgumentError.new "wrong number of arguments"
|
63
|
+
when 1
|
64
|
+
port = port.first
|
65
|
+
end
|
66
|
+
|
67
|
+
if port.is_a? Array then
|
68
|
+
port.each do |port|
|
69
|
+
pwm_disable port
|
70
|
+
end
|
71
|
+
return true
|
72
|
+
end
|
73
|
+
raise ArgumentError.new "Port must be an integer between 1 and #{@num_FETs}" unless port.is_a? Fixnum and (1..@num_FETs).include? port
|
74
|
+
|
75
|
+
port = 2**(port-1)
|
76
|
+
|
77
|
+
curPWM = read(0x5f, 1)[0]
|
78
|
+
tgtPWM = curPWM & ~port
|
79
|
+
write(0x5f, tgtPWM)
|
80
|
+
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
#Returns the ports that have PWM enabled
|
85
|
+
#
|
86
|
+
# @return [Array] An array containing the port numbers with PWM enabled
|
87
|
+
def pwm_ports
|
88
|
+
curPWM = read(0x5f, 1)[0]
|
89
|
+
|
90
|
+
ret = []
|
91
|
+
(1..@num_FETs).each do |port|
|
92
|
+
ret << port if curPWM & 2**(port-1) > 0
|
93
|
+
end
|
94
|
+
ret
|
95
|
+
end
|
96
|
+
|
97
|
+
#Checks if a port has PWM enabled
|
98
|
+
#
|
99
|
+
# @param [Number] port The port to check
|
100
|
+
# @return [Boolean] Is the port enabled for PWM control
|
101
|
+
def pwm_enabled?(port)
|
102
|
+
pwm_ports.include? port
|
103
|
+
end
|
104
|
+
|
105
|
+
#Read the current position of the stepper motor
|
106
|
+
#
|
107
|
+
# @return [Number] The current stepper position
|
108
|
+
def stepper_position
|
109
|
+
read(0x40, 4).pack("C*").unpack("l>")[0]
|
110
|
+
end
|
111
|
+
#Set the current position of the stepper motor, without actually moving it
|
112
|
+
#
|
113
|
+
# @param [Number] position The new position of the stepper motor
|
114
|
+
def stepper_position=(position)
|
115
|
+
raise ArgumentError.new "Position must be an integer" unless position.is_a? Fixnum
|
116
|
+
|
117
|
+
write(0x40, [position].pack("l>"))
|
118
|
+
end
|
119
|
+
|
120
|
+
#Read the target position of the stepper motor
|
121
|
+
#
|
122
|
+
# @return [Number] The target position of the stepper
|
123
|
+
def stepper_target
|
124
|
+
read(0x41, 4).pack("C*").unpack("l>")[0]
|
125
|
+
end
|
126
|
+
#Set the target position of the stepper motor
|
127
|
+
#
|
128
|
+
# @param [Number] position The target position for the stepper motor
|
129
|
+
def stepper_target=(position)
|
130
|
+
raise ArgumentError.new "Position must be an integer" unless position.is_a? Fixnum
|
131
|
+
|
132
|
+
write(0x41, [position].pack("l>"))
|
133
|
+
end
|
134
|
+
|
135
|
+
#Read the step delay of the stepper motor
|
136
|
+
#
|
137
|
+
# @return [Number] The stepdelay in tenths of a millisecond
|
138
|
+
def stepper_delay
|
139
|
+
read(0x43, 1)[0]
|
140
|
+
end
|
141
|
+
#Set the step delay of the stepper motor
|
142
|
+
#
|
143
|
+
# @param [Number] delay The new stepdelay, in tenths of a millisecond (maximum 255 - 25ms between steps)
|
144
|
+
def stepper_delay=(delay)
|
145
|
+
raise ArgumentError.new "Delay must be an integer between 0 and 255" unless delay.is_a? Fixnum and (0..255).include? delay
|
146
|
+
|
147
|
+
write(0x43, delay)
|
148
|
+
end
|
149
|
+
|
150
|
+
#Returns the PWM value on the specified port
|
151
|
+
#
|
152
|
+
# @param [Number] port The port to read the value from
|
153
|
+
# @return [Number] The PWM value on the port (0..255)
|
154
|
+
def [](port)
|
155
|
+
raise ArgumentError.new "Port must be an integer between 1 and #{@num_FETs}" unless port.is_a? Fixnum and (1..@num_FETs).include? port
|
156
|
+
|
157
|
+
return read(0x50 + (port-1), 1)[0]
|
158
|
+
end
|
159
|
+
|
160
|
+
#Sets the PWM value on the specified port
|
161
|
+
#
|
162
|
+
# @param [Number] port The port to set the value on
|
163
|
+
# @param [Number] value The PWM value to set (0..255)
|
164
|
+
def []=(port, value)
|
165
|
+
raise ArgumentError.new "Port must be an integer between 1 and #{@num_FETs}" unless port.is_a? Fixnum and (1..@num_FETs).include? port
|
166
|
+
raise ArgumentError.new "Value must be an integer between 0 and 255" unless value.is_a? Fixnum and (0..255).include? value
|
167
|
+
|
168
|
+
write(0x50 + (port-1), value)
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
Known_Boards[/(spi|i2c)_3fets/] = {
|
176
|
+
:default_address => 0x8a,
|
177
|
+
:constructor => Proc.new { |options| BitWizard::Boards::FETs.new options.merge({ :num => 3 }) },
|
178
|
+
:features => [ :input, :output, :stepper, :pwm ]
|
179
|
+
}
|
180
|
+
Known_Boards[/(spi|i2c)_7fets/] = {
|
181
|
+
:default_address => 0x88,
|
182
|
+
:constructor => Proc.new { |options| BitWizard::Boards::FETs.new options.merge({ :num => 7 }) },
|
183
|
+
:features => [ :input, :output, :stepper, :pwm ]
|
184
|
+
}
|
185
|
+
|
186
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitwizard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander "Ace" Olofsson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pi_piper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Ruby library for controlling the BitWizard boards over SPI and I2C
|
28
|
+
email: ace@haxalot.com
|
29
|
+
executables:
|
30
|
+
- bitwizardctl
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/bitwizard.rb
|
35
|
+
- lib/bitwizard/nfets.rb
|
36
|
+
- lib/bitwizard/motor.rb
|
37
|
+
- bin/bitwizardctl
|
38
|
+
homepage: https://github.com/ace13/BitWizard-Ruby
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 2.0.3
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: Ruby library for BitWizard boards
|
62
|
+
test_files: []
|