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.
@@ -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
@@ -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
@@ -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: []