bitwizard 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []