msp430_bsl 0.0.1

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