bitwizard 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|