msp430_bsl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ca5ed6ae2b592007808e73255f2f1de59c096f5b2e5702ad86fb7dea7c586981
4
+ data.tar.gz: 6c289eaece5e0fe084ddba9f5847c87da017c51db2825c44d4463a6a10db7e7f
5
+ SHA512:
6
+ metadata.gz: 327080fb207aabed5bdd0df53f84555268eaf251118c935ee9414547bd437df954b8704cfdd969e0a2cf60e296d9b01a9bf600222fd7a2c96a4f8ca423bf43bd
7
+ data.tar.gz: 3e8b706fc043fa9048b191e44a5deab69790202418dbbeb51ce93e1ea388637b61847c8cc79129ac6ab1d0a5d0cf4f79f4941263f408e975e9d6341058ea1fec
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Alessandro Verlato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # MSP430Bsl
2
+
3
+ This library is a base for developing MSP430 BSL-based utilities
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'msp430_bsl'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install msp430_bsl
20
+
21
+ ## Compatibility
22
+
23
+ Although it's not tested, this library should be compatibile with Ruby 2.7.0 or higher.
24
+
25
+ ## Usage
26
+
27
+ **Help is appreciated to write some good USAGE**
28
+
29
+ In the `bin` folder there's the `upload_hex` executable.
30
+
31
+ TL;DR: the script can upload a .hex file to the target through a normal UART connection (`rts` and `dtr` pins required).
32
+
33
+ **The script has been tested only with CC430F5137**
34
+
35
+ Just run `bin/upload_hex -h` to show available options.
36
+
37
+ ## TODO
38
+
39
+ * Write specs
40
+ * Add documentation
41
+ * Write a good Usage
42
+ * Add missing features and generalize the ones already present
43
+ * Delete this TODO section
44
+
45
+ ## Contributing
46
+
47
+ **Bug reports and pull requests are welcome!**
48
+
49
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
50
+ the [Contributor Covenant](contributor-covenant.org) code of conduct.
51
+
52
+
53
+ ## License
54
+
55
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
56
+
data/bin/upload_hex ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ Bundler.require
5
+ require 'slop'
6
+ require_relative '../lib/msp430_bsl'
7
+
8
+ begin
9
+ opts = Slop.parse help: true do |o|
10
+ o.string '-d', '--device', 'Mandatory: Path to serial programmer device', required: true
11
+ o.string '-f', '--hexfile', 'Mandatory: Path to HEX file to load', required: true
12
+ o.string '-g', '--logfile', 'Path to logfile'
13
+ o.string '-l', '--loglevel', "Logger level. One of ['fatal', 'error', 'warn', 'info', 'debug']. Defaults to 'debug'", default: :debug
14
+ o.bool '-c', '--check', 'Verify flash content after upload', default: true
15
+ o.bool '-h', '--help', 'Print this help' do
16
+ puts "#{o}\n"
17
+ exit
18
+ end
19
+ end
20
+ rescue Slop::MissingArgument => e
21
+ puts "Error: #{e}. Maybe you specified an empty argument?"
22
+ exit
23
+ rescue Slop::UnknownOption => e
24
+ puts "Error: #{e}"
25
+ exit
26
+ end
27
+
28
+ include Msp430Bsl::Utils
29
+
30
+ logger = build_logger_from opts
31
+
32
+ @board = Msp430Bsl::Uart::Connection.new opts[:device], logger: logger
33
+
34
+ # Enter BSL
35
+ @board.enter_bsl
36
+ # Mass erase FLASH
37
+ logger.info 'Mass erasing FLASH'
38
+ @board.send_command :mass_erase
39
+ # Unlock BSL protected commands
40
+ logger.info "'Unlocking BSL's password protected commands'"
41
+ @board.send_command :rx_password, data: Msp430Bsl::Configs::CMD_RX_PASSWORD
42
+ # Switch UART to max speed
43
+ logger.info 'Changing UART BAUD to 115200'
44
+ @board.send_command :change_baud_rate, data: Msp430Bsl::Configs::BAUD_RATES[115200]
45
+ @board.set_uart_speed 115200
46
+
47
+ # If everything has gone well so far...
48
+ hexfile = Msp430Bsl::HexFile.new opts[:hexfile]
49
+
50
+ # Group lines by contiguous memory addr
51
+ logger.info 'Writing data into FLASH'
52
+ line_groups = hexfile.data_lines_grouped_by_contiguous_addr
53
+ # Try to optimize BSL writes
54
+ # For each lines group, append as many lines as possible, given the BSL Core Commands buffer size
55
+ line_groups.each do |group|
56
+ curr_data_packet = []
57
+ curr_data_size = 0
58
+ # Cycle lines in a group
59
+ group.each do |line|
60
+ if curr_data_packet.empty?
61
+ # Use current line's addr as packet addr
62
+ curr_data_packet << line
63
+ curr_data_size += 2 + line.data_length # 2 is the addr size (2 bytes)
64
+ elsif (curr_data_size + line.data_length) <= Msp430Bsl::Uart::Connection::CORE_COMMANDS_BUFFER_SIZE
65
+ # If there's still room, append the line data
66
+ curr_data_packet << line
67
+ curr_data_size += line.data_length
68
+ else
69
+ # No room left, send packet
70
+ @board.send_command :rx_data_block, addr: curr_data_packet.first.addr, data: curr_data_packet.map { |line| line.data }.reduce(:+)
71
+ curr_data_packet = []
72
+ curr_data_size = 0
73
+ redo # Handle current line than would otherwise be skipped
74
+ end
75
+ end
76
+
77
+ # Send residual lines before handling next group
78
+ if curr_data_packet.any?
79
+ @board.send_command :rx_data_block, addr: curr_data_packet.first.addr, data: curr_data_packet.map { |line| line.data }.reduce(:+)
80
+ end
81
+ end
82
+
83
+ # TODO: Verify CRC or read entire flash and compare to hex lines?
84
+
85
+ logger.info 'Closing connection'
86
+ @board.close_connection
@@ -0,0 +1,13 @@
1
+ class Array
2
+ def to_hex
3
+ map { |el| el.to_hex_str }
4
+ end
5
+
6
+ def to_chr
7
+ map { |el| el.chr }
8
+ end
9
+
10
+ def to_chr_string
11
+ to_chr.join
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ class Numeric
2
+ def to_hex_str
3
+ n = to_s(16).upcase
4
+ if n.length.odd?
5
+ n = "0#{n}"
6
+ end
7
+ n
8
+ end
9
+
10
+ def to_bytes_ary
11
+ res = []
12
+ to_hex_str.chars.each_slice(2) { |byte| res << byte.join().to_i(16) }
13
+ res
14
+ end
15
+
16
+ def millis
17
+ self / 1_000.0
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ class String
2
+
3
+ def to_hex_ary
4
+ res = []
5
+ chars.each_slice(2) { |byte| res << byte.join().to_i(16) }
6
+ res
7
+ end
8
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ class Command
5
+
6
+ attr_accessor :name, :code, :addr, :data, :configs
7
+
8
+ def self.supports?(cmd_name)
9
+ Configs::CMDS.keys.include? cmd_name.to_sym
10
+ end
11
+
12
+ def self.[](cmd_name)
13
+ raise Exceptions::Command::NameNotSupported, cmd_name unless supports?(cmd_name)
14
+
15
+ Configs::CMDS[cmd_name.to_sym]
16
+ end
17
+
18
+ def initialize(cmd_name, addr: nil, data: nil)
19
+ raise Exceptions::Command::NameNotSupported, cmd_name unless self.class.supports?(cmd_name)
20
+
21
+ @name = cmd_name
22
+ @configs = self.class[@name]
23
+ @code = configs[:code]
24
+ @addr = addr
25
+ @data = data
26
+
27
+ validate
28
+ end
29
+
30
+ def packet
31
+ return @packet if @packet
32
+
33
+ @packet = [code, splitted_addr, data].flatten.compact
34
+ end
35
+
36
+ def length
37
+ code.to_bytes_ary.length
38
+ end
39
+
40
+ # Split address to [low, middle, high] bytes
41
+ def splitted_addr
42
+ if addr
43
+ [ (addr & 0xFF), ((addr >> 8) & 0xFF), ((addr >> 16) & 0xFF) ]
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def validate
50
+ # Check if command requires address and/or data
51
+ if configs[:requires_addr] && !addr
52
+ raise Exceptions::Command::RequiresAddr.new name
53
+ end
54
+
55
+ if configs[:requires_data] && !data
56
+ raise Exceptions::Command::RequiresData.new name
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ module Configs
5
+ CMD_KINDS = {
6
+ data: { code: 0x3A, payload_min_size: 2 },
7
+ message: { code: 0x3B, payload_size: 1 }
8
+ }.freeze
9
+
10
+ CMDS = {
11
+ rx_data_block: { code: 0x10, requires_addr: true, requires_data: true, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
12
+ rx_data_block_fast: { code: 0x1B, requires_addr: true, requires_data: true, response: { kind: nil, data_size: 1 }},
13
+ rx_password: { code: 0x11, requires_addr: false, requires_data: true, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
14
+ erase_segment: { code: 0x12, requires_addr: true, requires_data: false, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
15
+ lock_unlock_info: { code: 0x13, requires_addr: false , requires_data: false, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
16
+ reserved: { code: 0x14, requires_addr: false , requires_data: false, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
17
+ mass_erase: { code: 0x15, requires_addr: false , requires_data: false, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
18
+ crc_check: { code: 0x16, requires_addr: true , requires_data: true, response: { kind: CMD_KINDS[:data][:code], data_size: 2 }},
19
+ load_pc: { code: 0x17, requires_addr: true, requires_data: false, response: { kind: CMD_KINDS[:message][:code], data_size: 1 }},
20
+ tx_data_block: { code: 0x18, requires_addr: true, requires_data: true, response: { kind: CMD_KINDS[:data][:code], data_size_min: 1 }},
21
+ tx_bsl_version: { code: 0x19, requires_addr: false , requires_data: false, response: { kind: CMD_KINDS[:data][:code], data_size: 4 }},
22
+ tx_buffer_size: { code: 0x1A, requires_addr: false , requires_data: false, response: { kind: CMD_KINDS[:data][:code], data_size: 2 }},
23
+ change_baud_rate: { code: 0x52, requires_addr: false , requires_data: true, response: { kind: nil }}
24
+ }
25
+
26
+ RESPONSE_MESSAGES = {
27
+ success: { code: 0x00, reason: 'Operation Successful' },
28
+ flash_write_check_failed: { code: 0x01, reason: 'Flash Write Check Failed. After programming, a CRC is run on the programmed data. If the CRC does not match the expected result, this error is returned' },
29
+ flash_fail_bit_set: { code: 0x02, reason: "Flash Fail Bit Set. An operation set the FAIL bit in the flash controller (see the MSP430F5xx and MSP430F6xx Family User's Guide for more details on the flash fail bit)" },
30
+ voltage_changed: { code: 0x03, reason: "Voltage Change During Program. The VPE was set during the requested write operation (see the MSP430F5xx and MSP430F6xx Family User's Guide for more details on the VPE bit)" },
31
+ bsl_locked: { code: 0x04, reason: 'BSL Locked. The correct password has not yet been supplied to unlock the BSL' },
32
+ bsl_password_error: { code: 0x05, reason: 'BSL Password Error. An incorrect password was supplied to the BSL when attempting an unlock' },
33
+ byte_write_forbidden: { code: 0x06, reason: 'Byte Write Forbidden. This error is returned when a byte write is attempted in a flash area' },
34
+ unknown_command: { code: 0x07, reason: 'Unknown Command. The command given to the BSL was not recognized.' },
35
+ packet_too_large: { code: 0x08, reason: 'Packet Length Exceeds Buffer Size. The supplied packet length value is too large to be held in the BSL receive buffer' }
36
+ }
37
+
38
+ CMD_RX_PASSWORD = Array.new(32) { 0xFF }
39
+
40
+ BAUD_RATES = {
41
+ 9600 => 0x02,
42
+ 19200 => 0x03,
43
+ 38400 => 0x04,
44
+ 57600 => 0x05,
45
+ 115200 => 0x06
46
+ }.freeze
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ module Exceptions
5
+ module Command
6
+ class NameNotSupported < StandardError
7
+ def initialize(given_name)
8
+ message = "command '#{given_name}' not recognized. Supported commands: #{Configs::CMDS.keys}"
9
+ super(message)
10
+ end
11
+ end
12
+
13
+ class RequiresAddr < StandardError
14
+ def initialize(cmd_name)
15
+ msg = "command '#{cmd_name}' requires 'addr' param"
16
+ super(msg)
17
+ end
18
+ end
19
+
20
+ class RequiresData < StandardError
21
+ def initialize(cmd_name)
22
+ msg = "command '#{cmd_name}' requires 'data' param"
23
+ super(msg)
24
+ end
25
+ end
26
+ end
27
+
28
+ module Response
29
+ class KindNotSupported < StandardError
30
+ def initialize(kind)
31
+ message = "Response kind '0x#{kind.to_hex_str}' not recognized. Supported response kinds: #{Configs::CMD_KINDS.keys}"
32
+ super(message)
33
+ end
34
+ end
35
+
36
+ class NotValid < StandardError
37
+ def initialize(errors)
38
+ message = "Response not valid. Errors: \n\n#{errors.map { |err| " - #{err[1]}" }.join "\n" }\n"
39
+ super(message)
40
+ end
41
+ end
42
+
43
+ class WrongDataSize < StandardError
44
+ def initialize(data, size, min: false)
45
+ message = "payload with a size of '#{data.size}' doesn't satisfy the required #{min ? 'min' : ''}size of '#{size}'"
46
+ super(message)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ module Msp430Bsl
2
+ class HexFile
3
+
4
+ attr_reader :path, :raw_data
5
+
6
+ def initialize(path)
7
+ @path = File.expand_path path
8
+ @raw_data = File.read path
9
+ end
10
+
11
+ def lines
12
+ return @lines if @lines
13
+
14
+ @lines = []
15
+ raw_data.each_line.with_index { |line, i| @lines << HexLine.new(line, num: i) }
16
+ @lines
17
+ end
18
+
19
+ def data_lines_grouped_by_contiguous_addr
20
+ return @grouped_lines if @grouped_lines
21
+
22
+ @grouped_lines = []
23
+ curr_group = nil
24
+ curr_addr = nil
25
+ prev_line = nil
26
+ lines.each do |line|
27
+ next unless line.is_of_type? :data
28
+
29
+ if curr_addr.nil?
30
+ # We just started a new group
31
+ curr_addr = line.addr
32
+ curr_group = [line]
33
+ elsif line.has_addr_contiguous_to? prev_line
34
+ # We already have a current group
35
+ curr_group << line
36
+ curr_addr = line.addr
37
+ else
38
+ @grouped_lines << curr_group
39
+ curr_addr = nil
40
+ redo
41
+ end
42
+
43
+ prev_line = line
44
+ end
45
+
46
+ @grouped_lines << curr_group if curr_group.any?
47
+
48
+ @grouped_lines
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ class HexLine
5
+ include Utils
6
+
7
+ attr_reader :raw_data, :data_length, :addr, :type, :data, :crc, :number
8
+
9
+ RECORD_TYPES = {
10
+ data: 0x00,
11
+ eof: 0x01,
12
+ ext_seg_addr: 0x02,
13
+ start_seg_addr: 0x03,
14
+ ext_lin_addr: 0x04,
15
+ start_lin_addr: 0x05
16
+ }.freeze
17
+
18
+ def initialize(data, num: nil)
19
+ raise StandardError, 'raw_data must be a String' unless data.is_a?(String)
20
+
21
+ # Strip String, remove first char i.e. ':' and convert char couples to its hex value.
22
+ @raw_data = data.strip[1..-1].to_hex_ary
23
+
24
+ @data_length = raw_data[0]
25
+ @addr = (raw_data[1] << 8) | raw_data[2]
26
+ @type = raw_data[3]
27
+ @data = raw_data.slice 4, data_length
28
+ @crc = raw_data[-1]
29
+ @number = num
30
+ end
31
+
32
+ # Checks if this line's address is contiguous with the one of the given line
33
+ # Obviously this can be true only if the given line has an address that precedes this line's one
34
+ def has_addr_contiguous_to?(another_line)
35
+ another_line.addr + another_line.data_length == addr
36
+ end
37
+
38
+ def crc_ok?
39
+ crc == crc8(raw_data[0..-2])
40
+ end
41
+
42
+ def is_of_type?(ty)
43
+ ty = ty.to_sym
44
+ type == RECORD_TYPES[ty]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ class Response
5
+
6
+ class << self
7
+ def supports_kind?(kind)
8
+ Configs::CMD_KINDS.values.map { |val| val[:code] }.include? kind
9
+ end
10
+ end
11
+
12
+ attr_reader :kind, :data, :errors
13
+
14
+ def initialize(payload)
15
+ raise ArgumentError, 'payload must be an Array' unless payload.is_a?(Array)
16
+
17
+ @kind = payload[0]
18
+ raise Exceptions::Response::KindNotSupported, kind unless self.class.supports_kind?(kind)
19
+
20
+ @data = payload[1..-1]
21
+
22
+ if is_data?
23
+ raise Exceptions::Response::WrongDataSize.new(data, Configs::CMD_KINDS[:data][:payload_min_size]) if data.size < Configs::CMD_KINDS[:data][:payload_min_size]
24
+ else
25
+ raise Exceptions::Response::WrongDataSize.new(data, Configs::CMD_KINDS[:message][:payload_size]) if data.size != Configs::CMD_KINDS[:message][:payload_size]
26
+ end
27
+ end
28
+
29
+ def is_data?
30
+ kind == Configs::CMD_KINDS[:data]
31
+ end
32
+
33
+ def is_message?
34
+ kind == Configs::CMD_KINDS[:message][:code]
35
+ end
36
+
37
+ def is_ok_given_command?(command)
38
+ @errors = []
39
+
40
+ # Verify if response kind is compatible with sent command
41
+ unless kind == command.configs[:response][:kind]
42
+ @errors << [:kind, "Kind NOK. Expected response kind: 0x#{command.configs[:response][:kind].to_hex_str} - got: 0x#{kind.to_hex_str}"]
43
+ end
44
+ # Check response exact data size
45
+ if command.configs.has_key?(:data_size) && data.size != command.configs[:data_size]
46
+ @errors << [:data_size, "Data size NOK. Expected data size to be exactly '#{command.configs[:data_size]}' bytes, got '#{data.size}' bytes"]
47
+ end
48
+ # Check response min data size
49
+ if command.configs.has_key?(:data_size_min) && data.size < command.configs[:data_size_min]
50
+ @errors << [:data_size_min, "Min data size NOK. Expected data to have at least a size of '#{command.configs[:min_data_size]}' bytes, got '#{data.size}' bytes"]
51
+ end
52
+ # If kind is "message" check response code
53
+ if is_message?
54
+ success_code = Configs::RESPONSE_MESSAGES[:success][:code]
55
+ if data[0] != success_code # First (and only) data byte is message code
56
+ @errors << [:message_code, "Message code NOK. Expected message code '#{success_code}', got '#{data[0]}'"]
57
+ end
58
+ end
59
+
60
+ @errors.empty?
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ module Uart
5
+ class Ack
6
+
7
+ MESSAGES = {
8
+ 0x00 => { code: :ack, reason: 'ACK - Command correctly received' },
9
+ 0x51 => { code: :header_nok, reason: 'Header incorrect. The packet did not begin with the required 0x80 value' },
10
+ 0x52 => { code: :crc_nok, reason: 'Checksum incorrect. The packet did not have the correct checksum value' },
11
+ 0x53 => { code: :packet_size_zero, reason: 'Packet size zero. The size for the BSL core command was given as 0' },
12
+ 0x54 => { code: :packet_size_exceeds, reason: 'Packet size exceeds buffer. The packet size given is too big for the RX buffer' },
13
+ 0x55 => { code: :unkown_error, reason: 'Unknown error' },
14
+ 0x56 => { code: :unkown_baudrate, reason: 'Unknown baud rate. The supplied data for baud rate change is not a known value' }
15
+ }
16
+
17
+ attr_accessor :value, :message
18
+
19
+ def initialize(value)
20
+ raise Exceptions::Ack::MessageNotSupported, value unless MESSAGES.include?(value)
21
+
22
+ @value = value
23
+ @message = MESSAGES[@value]
24
+ end
25
+
26
+ def ok?
27
+ value == 0x00
28
+ end
29
+
30
+ def reason
31
+ message[:reason]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+ require 'timeout'
4
+
5
+ module Msp430Bsl
6
+ module Uart
7
+ class Connection
8
+
9
+ UART_CONFIGS = { data_bits: 8, stop_bits: 1, parity: SerialPort::EVEN }.transform_keys(&:to_s).freeze
10
+
11
+ WAIT_FOR_ACK_MAX = 100.millis
12
+ WAIT_FOR_RESPONSE_MAX = 100.millis
13
+
14
+ MEM_START_MAIN_FLASH = 0x8000
15
+
16
+ CORE_COMMANDS_BUFFER_SIZE = 260
17
+
18
+ attr_reader :serial_port, :device_path, :logger, :cmd_buff_size
19
+
20
+ def initialize(device_path, opts = {})
21
+ @device_path = device_path
22
+ @logger = opts.fetch :logger, Logger.new(STDOUT)
23
+ @cmd_buff_size = opts.fetch :cmd_buf_size, CORE_COMMANDS_BUFFER_SIZE
24
+
25
+ @serial_port = SerialPort.new @device_path
26
+ end
27
+
28
+ def close_connection
29
+ serial_port.close
30
+ end
31
+
32
+ def check_bsl_reply
33
+ reply.length > 1 && reply[0] == BSL_MESSAGE && reply[1] == BSL_OK
34
+ end
35
+
36
+ def enter_bsl
37
+ logger.info "Connecting to target board through UART on #{device_path}"
38
+ set_uart_speed 9600
39
+ invoke_bsl
40
+ end
41
+
42
+ def read_response_for(command)
43
+ raise ArgumentError, "an instance of Msp430Bsl::Command is expected as argument. Given: '#{command.class}'" unless command.is_a?(Command)
44
+
45
+ # Wait for first response byte - UART's ACK/NACK
46
+ ack = nil
47
+ begin
48
+ Timeout::timeout(WAIT_FOR_ACK_MAX) do
49
+ ack = Ack.new serial_port.getbyte
50
+ end
51
+ rescue Timeout::Error => e
52
+ logger.error 'Timeout occurred while waiting for UART ACK'
53
+ raise e
54
+ end
55
+
56
+ # If we arrived here, ack has been populated
57
+ if ack && ack.ok?
58
+ logger.debug "IN <- ACK (1 byte) 0x#{ack.value.to_hex_str}"
59
+ else
60
+ logger.error ack.reason
61
+ raise Exceptions::Ack::NOK, ack.value
62
+ end
63
+
64
+ # If this command has not response, return
65
+ unless command.configs[:response][:kind]
66
+ return ack
67
+ end
68
+
69
+ # Wait for command response
70
+ begin
71
+ pi = PeripheralInterface.new
72
+ Timeout::timeout(WAIT_FOR_RESPONSE_MAX) do
73
+ loop do
74
+ read = serial_port.readpartial cmd_buff_size
75
+ pi.push read.unpack 'C*'
76
+ break if pi.valid?
77
+ end
78
+ end
79
+ rescue Timeout::Error => e
80
+ logger.error 'Timeout occurred while fetching response from UART'
81
+ raise e
82
+ end
83
+
84
+ # Unwrap PeripheralInterface and create Response
85
+ logger.debug "IN <- RES (#{pi.packet.size} bytes) #{pi.to_hex_ary_str}"
86
+ response = pi.to_response
87
+ unless response.is_ok_given_command? command
88
+ raise Msp430Bsl::Exceptions::Response::NotValid, response.errors
89
+ end
90
+
91
+ response
92
+ end
93
+
94
+ def send_command(cmd_name, addr: nil, data: nil, log_only: false)
95
+ command = Command.new cmd_name, addr: addr, data: data
96
+ pi = PeripheralInterface.wrap command
97
+ logger.debug "Sending command '#{command.name}' over UART"
98
+ # Flush serial's output and input before sending a new command
99
+ serial_port.flush_output
100
+ serial_port.flush_input
101
+
102
+ unless pi.valid?
103
+ logger.error "PeripheralInterface not valid. Errors: #{pi.errors}"
104
+ return nil
105
+ end
106
+
107
+ logger.debug "OUT -> (#{pi.packet.size} bytes) #{pi.to_hex_ary_str}"
108
+ unless log_only
109
+ serial_port.write pi.to_uart
110
+ read_response_for command
111
+ end
112
+ end
113
+
114
+ def set_uart_speed(baud)
115
+ raise StandardError, "BAUD not supported. Supported BAUD: #{Configs::BAUD_RATES.keys}" unless Configs::BAUD_RATES.keys.include?(baud)
116
+
117
+ logger.debug "Setting serial port BAUD to #{baud} bps"
118
+
119
+ serial_port.set_modem_params UART_CONFIGS.merge('baud' => baud) # We must use strings as keys
120
+ test_pin_go :high
121
+ reset_pin_go :low
122
+ end
123
+
124
+ def trigger_reset
125
+ # slau319 pag. 5 - Fig. 1-1
126
+ reset_pin_go :low
127
+ test_pin_go :low
128
+ sleep 5.millis
129
+ reset_pin_go :high
130
+ end
131
+
132
+ private
133
+
134
+ def convert_pin(value, negate: false)
135
+ res = case value
136
+ when Symbol
137
+ value == :high ? 1 : 0
138
+ when Numeric
139
+ value == 1 ? 1 : 0
140
+ when TrueClass
141
+ 1
142
+ when FalseClass
143
+ 0
144
+ else
145
+ raise ArgumentError, 'convert_pin: value not supported'
146
+ end
147
+
148
+ if negate
149
+ res = res == 1 ? 0 : 1
150
+ end
151
+
152
+ res
153
+ end
154
+
155
+ def invoke_bsl
156
+ serial_port.flush_input
157
+ serial_port.flush_output
158
+
159
+ logger.info 'Entering BSL...'
160
+
161
+ test_pin_go(:low)
162
+ reset_pin_go(:low)
163
+ sleep 5.millis
164
+ 2.times do
165
+ test_pin_go(:high)
166
+ sleep 1.millis
167
+ test_pin_go(:low)
168
+ sleep 1.millis
169
+ end
170
+
171
+ test_pin_go(:high)
172
+ sleep 1.millis
173
+ reset_pin_go(:high)
174
+ sleep 1.millis
175
+ test_pin_go :low
176
+ sleep 50.millis # Give microcontroller time to enter BSL
177
+
178
+ logger.info 'OK, BSL ready'
179
+ end
180
+
181
+ def negate_pin(value)
182
+ raise ArgumentError, "value must be Numeric (0,1)" unless value.is_a?(Numeric)
183
+
184
+ value == 1 ? 0 : 1
185
+ end
186
+
187
+ def reset_pin_go(value)
188
+ serial_port.dtr = convert_pin value, negate: true
189
+ end
190
+
191
+ def test_pin_go(value)
192
+ serial_port.rts = convert_pin value, negate: true
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ module Uart
5
+ module Exceptions
6
+ module Ack
7
+
8
+ class NOK < StandardError
9
+ def initialize(val)
10
+ message = "Command ACK not OK. Received value: #{val}"
11
+ super(message)
12
+ end
13
+ end
14
+ class MessageNotSupported < StandardError
15
+ def initialize(value)
16
+ message = "message with value 0x#{value.to_hex_str} not supported"
17
+ super(message)
18
+ end
19
+ end
20
+ end
21
+
22
+ module PeripheralInterface
23
+ class InterfaceWrapNotACommand < StandardError
24
+ def initialize(arg)
25
+ message = "Given argument '#{arg}' must be a Msp430Bsl::Command"
26
+ super(message)
27
+ end
28
+ end
29
+
30
+ class InterfaceParseRawDataNotArray < StandardError
31
+ def initialize
32
+ message = "PeripheralInterface#parse argument must be an Array"
33
+ super(message)
34
+ end
35
+ end
36
+
37
+ class InterfaceDataNotArray < StandardError
38
+ def initialize
39
+ message = "Peripheral Interface 'data' argument must be an Array"
40
+ super(message)
41
+ end
42
+ end
43
+
44
+ class InterfaceSize < StandardError
45
+ def initialize(received_size)
46
+ message = "Peripheral Interface size error. Required packet's min size is '#{PeripheralInterface::MIN_PACKET_SIZE}' bytes, given raw_data size is '#{received_size}' bytes"
47
+ super(message)
48
+ end
49
+ end
50
+
51
+ class InterfaceHeaderNOK < StandardError
52
+ def initialize(received_header)
53
+ message = "Peripheral interface header NOK. Received '0x#{received_header.to_hex_str}' instead of 0x#{PeripheralInterface::OK_HEADER.to_hex_str}"
54
+ super(message)
55
+ end
56
+
57
+ end
58
+
59
+ class InterfaceCRCMismatch < StandardError
60
+ def initialize
61
+ message = 'Peripheral Interface CRC mismatch'
62
+ super(message)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Msp430Bsl
4
+ module Uart
5
+ class PeripheralInterface
6
+ include Utils
7
+
8
+ MIN_PACKET_SIZE = 6.freeze # bytes
9
+ OK_HEADER = 0x80.freeze
10
+
11
+ HEADER_SIZE = 1.freeze
12
+ DATA_LEN_SIZE = 2.freeze
13
+ CRC_SIZE = 2.freeze
14
+
15
+ class << self
16
+ include Utils
17
+
18
+ def wrap(command)
19
+ unless command.is_a?(Command)
20
+ raise Exceptions::PeripheralInterfaceWrapNotACommand, command
21
+ end
22
+
23
+ new header: OK_HEADER, data_len: command.packet.size, data: command.packet
24
+ end
25
+
26
+ def parse(raw_data)
27
+ raise Exceptions::PeripheralInterfaceParseRawDataNotArray unless raw_data.is_a?(Array)
28
+ raise Exceptions::PeripheralInterfaceSize, raw_data.size unless raw_data.size >= MIN_PACKET_SIZE
29
+
30
+ header = raw_data[0]
31
+ data_len = raw_data[2] << 8 | raw_data[1]
32
+ data = raw_data[3, data_len]
33
+ crc = raw_data[-1] << 8 | raw_data[-2]
34
+
35
+ new header: header, data_len: data_len, data: data, crc: crc
36
+ end
37
+ end
38
+
39
+ attr_reader :header, :data_len, :data, :crc, :errors, :packet, :cmd_kind
40
+
41
+ def initialize(header: nil, data_len: nil, data: nil, crc: nil)
42
+ raise Exceptions::PeripheralInterfaceDataNotArray if (data && !data.is_a?(Array))
43
+
44
+ @header = header
45
+ @data_len = data_len
46
+ @data = data
47
+ @crc = crc ? crc : (data ? crc16(data) : nil)
48
+ @cmd_code
49
+
50
+ @partial_data = []
51
+ end
52
+
53
+ def crc_ok?
54
+ data && crc == crc16(data)
55
+ end
56
+
57
+ def header_ok?
58
+ header == OK_HEADER
59
+ end
60
+
61
+ def data_len_ok?
62
+ data&.length == data_len
63
+ end
64
+
65
+ def length
66
+ packet.length
67
+ end
68
+
69
+ def packet
70
+ res = data.clone
71
+ cmd_len = res.length
72
+ # Calculate CRC (it must be calculated only on command data)
73
+ crc = crc16 res
74
+ # Prepend preamble
75
+ res.prepend 0x80, (cmd_len & 0xFF), ((cmd_len >> 8) & 0xFF)
76
+ # Append CRC16
77
+ res.append (crc & 0xFF), ((crc >> 8) & 0xFF)
78
+ end
79
+
80
+ def push(val)
81
+ @partial_data.append *val
82
+
83
+ # If :header has not already been fetched
84
+ if !header && @partial_data.size >= HEADER_SIZE
85
+ @header = @partial_data.shift
86
+ end
87
+ # If :data_len has not already been fetched, and we have enough data
88
+ if header && !data_len && @partial_data.size >= DATA_LEN_SIZE
89
+ values = @partial_data.shift DATA_LEN_SIZE
90
+ @data_len = values[0] | (values[1] << 8)
91
+ end
92
+
93
+ # If :data has not already been fetched, fetch it
94
+ if data_len && (data.nil? || data.empty?) && @partial_data.size >= data_len
95
+ @data = @partial_data.shift data_len
96
+ end
97
+
98
+ if data && !crc && @partial_data.size >= CRC_SIZE
99
+ values = @partial_data.shift CRC_SIZE
100
+ @crc = values[0] | (values[1] << 8)
101
+ end
102
+ end
103
+ alias_method :<<, :push
104
+
105
+ def to_hex_ary_str
106
+ packet.to_hex
107
+ end
108
+
109
+ def to_uart
110
+ packet.to_chr_string
111
+ end
112
+
113
+ def to_response
114
+ Response.new data
115
+ end
116
+
117
+ def valid?
118
+ @errors = []
119
+ @errors << [:header, 'Header NOK'] unless header_ok?
120
+ @errors << [:data_len, "'data_len' value (#{data_len}) differs from current data length (#{data&.length})"] unless data_len_ok?
121
+ @errors << [:crc, 'CRC NOK'] unless crc_ok?
122
+
123
+ @errors.empty?
124
+ end
125
+
126
+ private
127
+
128
+ def parse_raw_data
129
+ @header = raw_data[0]
130
+ @data_len = raw_data[2] << 8 | raw_data[1]
131
+ @data = raw_data[3, data_len]
132
+ @crc = raw_data[-1] << 8 | raw_data[-2]
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+
4
+ module Msp430Bsl
5
+ module Utils
6
+ def crc16(data)
7
+ raise ArgumentError, 'data must be an Array' unless data.is_a?(Array)
8
+
9
+ crc = 0xFFFF
10
+
11
+ data.each do |byte|
12
+ x = (crc >> 8 ^ byte) & 0xFF
13
+ x ^= x >> 4
14
+ crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x
15
+ end
16
+
17
+ crc & 0xFFFF
18
+ end
19
+
20
+ def crc8(data)
21
+ (~data.reduce(:+) + 1) & 0xFF
22
+ end
23
+
24
+ def build_logger_from(opts)
25
+ logto = if opts[:logfile]
26
+ File.expand_path opts[:logfile]
27
+ else
28
+ STDOUT
29
+ end
30
+
31
+ Logger.new logto, level: normalize_log_level(opts[:loglevel])
32
+ end
33
+
34
+ def normalize_log_level(level)
35
+ case level
36
+ when :debug, ::Logger::DEBUG, 'debug', 'd' then ::Logger::DEBUG
37
+ when :info, ::Logger::INFO, 'info', 'i' then ::Logger::INFO
38
+ when :warn, ::Logger::WARN, 'warn', 'w' then ::Logger::WARN
39
+ when :error, ::Logger::ERROR, 'error', 'e' then ::Logger::ERROR
40
+ when :fatal, ::Logger::FATAL, 'fatal', 'f' then ::Logger::FATAL
41
+ else
42
+ ENV['LOG_LEVEL'] || Logger::DEBUG
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Msp430Bsl
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/lib/msp430_bsl.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'zeitwerk'
2
+ require 'serialport'
3
+
4
+ loader = Zeitwerk::Loader.new
5
+ loader.push_dir File.expand_path(__dir__)
6
+ core_ext = File.expand_path('core_ext', __dir__)
7
+ loader.ignore core_ext
8
+ loader.setup
9
+
10
+ # Require core_ext files
11
+ Dir["#{core_ext}/**/*.rb"].each { |file| require file }
12
+
13
+ module Msp430Bsl
14
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: msp430_bsl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alessandro Verlato
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: zeitwerk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.6.8
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.6.8
27
+ - !ruby/object:Gem::Dependency
28
+ name: serialport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: slop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 4.10.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 4.10.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.14.2
69
+ description:
70
+ email:
71
+ - averlato@gmail.com
72
+ executables:
73
+ - upload_hex
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - MIT-LICENSE
78
+ - README.md
79
+ - bin/upload_hex
80
+ - lib/core_ext/array.rb
81
+ - lib/core_ext/number.rb
82
+ - lib/core_ext/string.rb
83
+ - lib/msp430_bsl.rb
84
+ - lib/msp430_bsl/command.rb
85
+ - lib/msp430_bsl/configs.rb
86
+ - lib/msp430_bsl/exceptions.rb
87
+ - lib/msp430_bsl/hex_file.rb
88
+ - lib/msp430_bsl/hex_line.rb
89
+ - lib/msp430_bsl/response.rb
90
+ - lib/msp430_bsl/uart/ack.rb
91
+ - lib/msp430_bsl/uart/connection.rb
92
+ - lib/msp430_bsl/uart/exceptions.rb
93
+ - lib/msp430_bsl/uart/peripheral_interface.rb
94
+ - lib/msp430_bsl/utils.rb
95
+ - lib/msp430_bsl/version.rb
96
+ homepage: https://github.com/madAle/msp430_bsl
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: 2.7.0
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.4.6
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Texas Instrument MSP430 BSL Ruby library
119
+ test_files: []