├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .github_changelog_generator ├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── README.md ├── Rakefile ├── STYLE.md ├── VERSION ├── doc └── Doxyfile ├── examples ├── Basic │ └── Basic.pde ├── PhoenixContact_nanoLC │ └── PhoenixContact_nanoLC.pde └── RS485_HalfDuplex │ └── RS485_HalfDuplex.ino ├── extras ├── ModbusMaster reference-2.0.1.pdf └── README.txt ├── keywords.txt ├── library.properties └── src ├── ModbusMaster.cpp ├── ModbusMaster.h └── util ├── crc16.h └── word.h /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | ### ModbusMaster version 13 | [Version of the project where you are encountering the issue] 14 | 15 | ### Arduino IDE version 16 | [Version of Arduino IDE in your environment] 17 | 18 | ### Arduino Hardware 19 | [Hardware information, including board and processor] 20 | 21 | ### Platform Details 22 | [Operating system distribution and release version] 23 | 24 | --- 25 | 28 | ### Scenario: 29 | [What you are trying to achieve and you can't?] 30 | 31 | ### Steps to Reproduce: 32 | [If you are filing an issue what are the things we need to do in order to repro your problem? How are you using this project or any resources it includes?] 33 | 34 | ### Expected Result: 35 | [What are you expecting to happen as the consequence of above reproduction steps?] 36 | 37 | ### Actual Result: 38 | [What actually happens after the reproduction steps? Include the error output or a link to a gist if possible.] 39 | 40 | --- 41 | 45 | ### Feature Request 46 | 47 | #### Narrative: 48 | 49 | ```` text 50 | As a [role] 51 | I want [feature] 52 | So that [benefit] 53 | ```` 54 | 55 | #### Acceptance Criteria: 56 | 59 | ```` text 60 | Scenario 1: Title 61 | Given [context] 62 | And [some more context]... 63 | When [event] 64 | Then [outcome] 65 | And [another outcome]... 66 | ```` 67 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | ### Description 9 | [Describe what this change achieves] 10 | 11 | ### Issues Resolved 12 | [List any existing issues this PR resolves; include Fixes #xxx or Closes #xxx (where xxx is issue number)] 13 | 14 | ### Check List 15 | 16 | General 17 | 18 | - [ ] Code follows coding style defined in STYLE.md 19 | - [ ] Doxygen comments are included inline with code 20 | - [ ] No unnecessary whitespace; check with `git diff --check` before committing. 21 | 22 | The following have been modified to reflect new features, if warranted 23 | 24 | - [ ] README.md 25 | - [ ] keywords.txt (use tabs as whitespace separators) 26 | - [ ] library.properties 27 | - [ ] examples/ - update or create new ones, as warranted 28 | 29 | The following have **NOT** been modified 30 | 31 | - [ ] doc/ - will be updated upon versioned release 32 | - [ ] .ruby-gemset 33 | - [ ] .ruby-version 34 | - [ ] CHANGELOG.md - will be updated upon versioned release (HISTORY.md is deprecated) 35 | - [ ] Gemfile 36 | - [ ] LICENSE 37 | - [ ] VERSION - will be updated upon versioned release 38 | -------------------------------------------------------------------------------- /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | add_issues_wo_labels=false 2 | add_pr_wo_labels=false 3 | enhancement-labels=Type: Enhancement 4 | bug-labels=Type: Bug 5 | exclude-labels=Type: Question 6 | header=# ModbusMaster CHANGELOG 7 | include-labels=Type: Bug,Type: Enhancement,Type: Feature Request,Type: Maintenance 8 | future-release=Unreleased 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------- ModbusMaster 2 | doc/html/ 3 | doc/latex/ 4 | 5 | 6 | #-------------- https://github.com/github/gitignore/blob/master/Ruby.gitignore 7 | *.gem 8 | *.rbc 9 | /.config 10 | /coverage/ 11 | /InstalledFiles 12 | /pkg/ 13 | /spec/reports/ 14 | /spec/examples.txt 15 | /test/tmp/ 16 | /test/version_tmp/ 17 | /tmp/ 18 | 19 | # Used by dotenv library to load environment variables. 20 | # .env 21 | 22 | ## Specific to RubyMotion: 23 | .dat* 24 | .repl_history 25 | build/ 26 | *.bridgesupport 27 | build-iPhoneOS/ 28 | build-iPhoneSimulator/ 29 | 30 | ## Specific to RubyMotion (use of CocoaPods): 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # vendor/Pods/ 37 | 38 | ## Documentation cache and generated files: 39 | /.yardoc/ 40 | /_yardoc/ 41 | /doc/ 42 | /rdoc/ 43 | 44 | ## Environment normalization: 45 | /.bundle/ 46 | /vendor/bundle 47 | /lib/bundler/man/ 48 | 49 | # for a library or gem, you might want to ignore these files since the code is 50 | # intended to run in multiple environments; otherwise, check them in: 51 | # Gemfile.lock 52 | # .ruby-version 53 | # .ruby-gemset 54 | 55 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 56 | .rvmrc 57 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | global 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 2.7 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - ~/.platformio 11 | 12 | # update Makefile if target boards added 13 | env: 14 | - PLATFORMIO_BOARD=uno 15 | - PLATFORMIO_BOARD=due 16 | - PLATFORMIO_BOARD=huzzah 17 | - PLATFORMIO_BOARD=genuino101 18 | - PLATFORMIO_BOARD=teensy31 19 | 20 | install: 21 | - pip install -U platformio 22 | 23 | before_script: 24 | - env 25 | - echo $HOME 26 | - echo $TRAVIS_BUILD_DIR 27 | - ls -al $PWD 28 | 29 | script: 30 | - make build 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ModbusMaster CHANGELOG 2 | 3 | ## [v2.0.0](https://github.com/4-20ma/ModbusMaster/tree/v2.0.0) (2016-09-24) 4 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v1.0.0...v2.0.0) 5 | 6 | **Implemented enhancements:** 7 | 8 | - BREAK: Update library to match IDE 1.5 spec v2.1 [\#81](https://github.com/4-20ma/ModbusMaster/pull/81) ([4-20ma](https://github.com/4-20ma)) 9 | - Use platformio to build multiple boards [\#79](https://github.com/4-20ma/ModbusMaster/pull/79) ([4-20ma](https://github.com/4-20ma)) 10 | 11 | **Closed issues:** 12 | 13 | - Use platformio to build against multiple boards [\#78](https://github.com/4-20ma/ModbusMaster/issues/78) 14 | - Add ruby files to .gitignore [\#75](https://github.com/4-20ma/ModbusMaster/issues/75) 15 | - Reorder Installation section of README [\#73](https://github.com/4-20ma/ModbusMaster/issues/73) 16 | - Update README [\#71](https://github.com/4-20ma/ModbusMaster/issues/71) 17 | - Rename HISTORY to CHANGELOG [\#47](https://github.com/4-20ma/ModbusMaster/issues/47) 18 | - Update library to match 1.5 specification [\#14](https://github.com/4-20ma/ModbusMaster/issues/14) 19 | 20 | **Merged pull requests:** 21 | 22 | - Use relative path to examples [\#80](https://github.com/4-20ma/ModbusMaster/pull/80) ([4-20ma](https://github.com/4-20ma)) 23 | - Add files to .gitignore [\#76](https://github.com/4-20ma/ModbusMaster/pull/76) ([4-20ma](https://github.com/4-20ma)) 24 | - Reorder installation section of README [\#74](https://github.com/4-20ma/ModbusMaster/pull/74) ([4-20ma](https://github.com/4-20ma)) 25 | - Update README [\#72](https://github.com/4-20ma/ModbusMaster/pull/72) ([4-20ma](https://github.com/4-20ma)) 26 | - Automate CHANGELOG generation [\#68](https://github.com/4-20ma/ModbusMaster/pull/68) ([4-20ma](https://github.com/4-20ma)) 27 | 28 | ## [v1.0.0](https://github.com/4-20ma/ModbusMaster/tree/v1.0.0) (2016-09-12) 29 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.11.0...v1.0.0) 30 | 31 | **Implemented enhancements:** 32 | 33 | - Add LICENSE, convert project to Apache 2.0 [\#67](https://github.com/4-20ma/ModbusMaster/pull/67) ([4-20ma](https://github.com/4-20ma)) 34 | - Add example sketch for half-duplex RS485 [\#66](https://github.com/4-20ma/ModbusMaster/pull/66) ([kintel](https://github.com/kintel)) 35 | - Add continuous integration testing with Travis CI [\#63](https://github.com/4-20ma/ModbusMaster/pull/63) ([4-20ma](https://github.com/4-20ma)) 36 | - Disable \_\_MODBUSMASTER\_DEBUG\_\_ mode by default [\#43](https://github.com/4-20ma/ModbusMaster/pull/43) ([kintel](https://github.com/kintel)) 37 | 38 | **Closed issues:** 39 | 40 | - Fix documentation references in ModbusMaster.h, .cpp [\#69](https://github.com/4-20ma/ModbusMaster/issues/69) 41 | - Clean up template wording [\#64](https://github.com/4-20ma/ModbusMaster/issues/64) 42 | - Add Label section to CONTRIBUTING [\#60](https://github.com/4-20ma/ModbusMaster/issues/60) 43 | - Update README contact information [\#58](https://github.com/4-20ma/ModbusMaster/issues/58) 44 | - Add continuous integration testing with travis [\#55](https://github.com/4-20ma/ModbusMaster/issues/55) 45 | - Add Code of Conduct [\#54](https://github.com/4-20ma/ModbusMaster/issues/54) 46 | - Create PULL\_REQUEST\_TEMPLATE [\#49](https://github.com/4-20ma/ModbusMaster/issues/49) 47 | - Create ISSUE\_TEMPLATE [\#48](https://github.com/4-20ma/ModbusMaster/issues/48) 48 | - Change license to Apache 2.0 [\#45](https://github.com/4-20ma/ModbusMaster/issues/45) 49 | - Set \_\_MODBUSMASTER\_DEBUG\_\_ to 0 by default [\#35](https://github.com/4-20ma/ModbusMaster/issues/35) 50 | - Pass Stream object instead of integer reference [\#17](https://github.com/4-20ma/ModbusMaster/issues/17) 51 | 52 | **Merged pull requests:** 53 | 54 | - Add documentation cross-references [\#70](https://github.com/4-20ma/ModbusMaster/pull/70) ([4-20ma](https://github.com/4-20ma)) 55 | - Clean up ISSUE/PULL\_REQUEST templates [\#65](https://github.com/4-20ma/ModbusMaster/pull/65) ([4-20ma](https://github.com/4-20ma)) 56 | - Add initial .travis.yml configuration [\#62](https://github.com/4-20ma/ModbusMaster/pull/62) ([4-20ma](https://github.com/4-20ma)) 57 | - Add label guidance to CONTRIBUTING [\#61](https://github.com/4-20ma/ModbusMaster/pull/61) ([4-20ma](https://github.com/4-20ma)) 58 | - Update README contact information [\#59](https://github.com/4-20ma/ModbusMaster/pull/59) ([4-20ma](https://github.com/4-20ma)) 59 | - Add email address to CODE\_OF\_CONDUCT [\#57](https://github.com/4-20ma/ModbusMaster/pull/57) ([4-20ma](https://github.com/4-20ma)) 60 | - Add CODE\_OF\_CONDUCT [\#56](https://github.com/4-20ma/ModbusMaster/pull/56) ([4-20ma](https://github.com/4-20ma)) 61 | - Add initial PULL\_REQUEST\_TEMPLATE [\#53](https://github.com/4-20ma/ModbusMaster/pull/53) ([4-20ma](https://github.com/4-20ma)) 62 | - Clarify instructions in ISSUE\_TEMPLATE [\#52](https://github.com/4-20ma/ModbusMaster/pull/52) ([4-20ma](https://github.com/4-20ma)) 63 | - Add ISSUE\_TEMPLATE title reqs, separator lines [\#51](https://github.com/4-20ma/ModbusMaster/pull/51) ([4-20ma](https://github.com/4-20ma)) 64 | - Add initial ISSUE\_TEMPLATE [\#50](https://github.com/4-20ma/ModbusMaster/pull/50) ([4-20ma](https://github.com/4-20ma)) 65 | - Add preTransmission\(\), postTransmission\(\) for half-duplex [\#44](https://github.com/4-20ma/ModbusMaster/pull/44) ([kintel](https://github.com/kintel)) 66 | - Add STYLE coding style guide [\#29](https://github.com/4-20ma/ModbusMaster/pull/29) ([4-20ma](https://github.com/4-20ma)) 67 | 68 | ## [v0.11.0](https://github.com/4-20ma/ModbusMaster/tree/v0.11.0) (2015-05-22) 69 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.10.3...v0.11.0) 70 | 71 | **Implemented enhancements:** 72 | 73 | - Update architecture switch [\#28](https://github.com/4-20ma/ModbusMaster/pull/28) ([4-20ma](https://github.com/4-20ma)) 74 | 75 | **Closed issues:** 76 | 77 | - Update architecture switch to match Arduino convention [\#27](https://github.com/4-20ma/ModbusMaster/issues/27) 78 | - Request timeout is impatient [\#3](https://github.com/4-20ma/ModbusMaster/issues/3) 79 | 80 | ## [v0.10.3](https://github.com/4-20ma/ModbusMaster/tree/v0.10.3) (2015-05-22) 81 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.10.2...v0.10.3) 82 | 83 | **Closed issues:** 84 | 85 | - Inconsistent Doxygen comments [\#25](https://github.com/4-20ma/ModbusMaster/issues/25) 86 | - Replace C macros with inline functions [\#18](https://github.com/4-20ma/ModbusMaster/issues/18) 87 | 88 | **Merged pull requests:** 89 | 90 | - Adjust doxygen comments to be consistent [\#26](https://github.com/4-20ma/ModbusMaster/pull/26) ([4-20ma](https://github.com/4-20ma)) 91 | - Replace C macros w/inline functions [\#24](https://github.com/4-20ma/ModbusMaster/pull/24) ([4-20ma](https://github.com/4-20ma)) 92 | 93 | ## [v0.10.2](https://github.com/4-20ma/ModbusMaster/tree/v0.10.2) (2015-05-22) 94 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.9.1...v0.10.2) 95 | 96 | **Implemented enhancements:** 97 | 98 | - Implement CRC16 for SAM3X8E microprocessor [\#11](https://github.com/4-20ma/ModbusMaster/pull/11) ([4-20ma](https://github.com/4-20ma)) 99 | - Add rx flush, change response timeout to 2000 ms [\#10](https://github.com/4-20ma/ModbusMaster/pull/10) ([agprimatic](https://github.com/agprimatic)) 100 | 101 | **Fixed bugs:** 102 | 103 | - Fix documentation build error [\#23](https://github.com/4-20ma/ModbusMaster/pull/23) ([4-20ma](https://github.com/4-20ma)) 104 | - Work around HardwareSerial for SAM3 micro [\#12](https://github.com/4-20ma/ModbusMaster/pull/12) ([4-20ma](https://github.com/4-20ma)) 105 | 106 | **Merged pull requests:** 107 | 108 | - Update pointers to match C++ convention [\#22](https://github.com/4-20ma/ModbusMaster/pull/22) ([4-20ma](https://github.com/4-20ma)) 109 | - Rename markdown file extensions [\#21](https://github.com/4-20ma/ModbusMaster/pull/21) ([4-20ma](https://github.com/4-20ma)) 110 | 111 | ## [v0.9.1](https://github.com/4-20ma/ModbusMaster/tree/v0.9.1) (2013-01-02) 112 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.9...v0.9.1) 113 | 114 | ## [v0.9](https://github.com/4-20ma/ModbusMaster/tree/v0.9) (2011-12-27) 115 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.8...v0.9) 116 | 117 | ## [v0.8](https://github.com/4-20ma/ModbusMaster/tree/v0.8) (2011-11-09) 118 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.7...v0.8) 119 | 120 | ## [v0.7](https://github.com/4-20ma/ModbusMaster/tree/v0.7) (2010-02-10) 121 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.6...v0.7) 122 | 123 | ## [v0.6](https://github.com/4-20ma/ModbusMaster/tree/v0.6) (2010-02-05) 124 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.5...v0.6) 125 | 126 | ## [v0.5](https://github.com/4-20ma/ModbusMaster/tree/v0.5) (2010-01-30) 127 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.4...v0.5) 128 | 129 | ## [v0.4](https://github.com/4-20ma/ModbusMaster/tree/v0.4) (2010-01-30) 130 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.3...v0.4) 131 | 132 | ## [v0.3](https://github.com/4-20ma/ModbusMaster/tree/v0.3) (2010-01-29) 133 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.2...v0.3) 134 | 135 | ## [v0.2](https://github.com/4-20ma/ModbusMaster/tree/v0.2) (2010-01-26) 136 | [Full Changelog](https://github.com/4-20ma/ModbusMaster/compare/v0.1...v0.2) 137 | 138 | ## [v0.1](https://github.com/4-20ma/ModbusMaster/tree/v0.1) (2010-01-25) 139 | 140 | 141 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project owner at 4-20ma@wvfans.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Project Maintainers 69 | 70 | - Doc Walker <<4-20ma@wvfans.net>> 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 75 | available at [http://contributor-covenant.org/version/1/4][version] 76 | 77 | [homepage]: http://contributor-covenant.org 78 | [version]: http://contributor-covenant.org/version/1/4/ 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | - Fork, then clone the repo: 5 | ```` 6 | git clone git@github.com:your_username/ModbusMaster.git 7 | ```` 8 | 9 | - Create a topic branch from where you want to base your work 10 | - This is usually the master branch 11 | - Only target release branches if you are certain your fix must be on that branch 12 | - To quickly create a topic branch based on master; `git checkout -b fix/master/my_contribution master`. Please avoid working directly on the `master` branch. 13 | 14 | - Follow the [style guide](https://github.com/4-20ma/ModbusMaster/blob/master/STYLE.md) 15 | 16 | - Test your change 17 | 18 | ```` bash 19 | $ make 20 | ```` 21 | 22 | Project must build successfully using `make` in order for contribution to be considered. 23 | 24 | - Make commits of logical units 25 | - Check for unnecessary whitespace with `git diff --check` before committing 26 | - Each commit should represent one atomic change and should stand on its own 27 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 28 | 29 | - Push to your fork and [submit a pull request](https://github.com/4-20ma/ModbusMaster/compare/) 30 | - [Code of conduct](https://github.com/4-20ma/ModbusMaster/blob/master/CODE_OF_CONDUCT.md) 31 | 32 | ### Labels 33 | 34 | Project maintainers assign labels to Issues and Pull Requests (PRs) to categorize, prioritize, and provide status. The following guidelines and conventions are used in this project: 35 | 36 | #### Type 37 | 38 | - `Bug` - existing code does not behave as described in the project documentation; _requires_ clear test case and be _reproducible_ by project maintainer 39 | - `Enhancement` - improvement to an existing feature (Issue or Pull Request) 40 | - `Feature Requst` - new functionality; _requires_ a well-written, clear user story (Issue) 41 | - `Maintenance` - minor administrative change that does not provide enhancement or introduce new feature 42 | - `Question` - self-explanatory 43 | 44 | #### Priority 45 | 46 | - `Low` - default priority; new issues generally start here 47 | - `Medium` - issues are escalated, depending on severity of the issue 48 | - `High` - issues are escalated, depending on severity of the issue 49 | - `Critical` - these issues are to be resolved ahead of any other 50 | 51 | #### Status 52 | 53 | - `Abandoned` - issue/PR closed due to inactivity 54 | - `Blocked` - issue/PR will not be resolved/merged (some projects label these items as `wontfix`; include explanation in issue/PR) 55 | - `In Progress` - issue has been assigned and is actively being addressed; re-label issue `On Hold` with explanation if there will be a significant delay 56 | - `Maintainer Review Needed` - last step prior to merge; PR passes continuous integration tests and is able to be cleanly merged - awaiting review for style, code cleanliness, etc. 57 | - `On Hold` - implementation delayed; provide explanation in issue/PR 58 | - `Pending Contributor Response` - issue/PR closed after 14 days of inactivity (re-label `Abandoned` at closure) 59 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Gemfile style guide derived from: 3 | # http://mcdowall.info/posts/gemfile-best-practices-and-discourse/ 4 | 5 | # Use `bundle install` after changing this file 6 | # `bundle update [gemname]` to force update of gem 7 | # `bundle show [gemname]` to see where a bundled gem is installed 8 | # `bundle open [gemname]` to edit a bundled gem 9 | # `bundle package` to add gem to vendor/cache 10 | 11 | source 'https://rubygems.org' 12 | 13 | 14 | # place gems sourced from github.com in this section _________________________ 15 | 16 | 17 | # place gems sourced from a project path in this section _____________________ 18 | 19 | 20 | # place general project gems in this section (alphabetical order) ____________ 21 | gem 'git', '~> 1.3.0' # git management 22 | gem 'github_changelog_generator', '~> 1.13.1' 23 | gem 'rake', '~> 11.2.2' 24 | gem 'version', '~> 1.0.0' # version management gem 25 | 26 | 27 | # place gems related to test/specs in this section (alphabetical order) ______ 28 | 29 | 30 | # place gems related to development in this section (alphabetical order) _____ 31 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.4.0) 5 | colorize (0.8.1) 6 | descendants_tracker (0.0.4) 7 | thread_safe (~> 0.3, >= 0.3.1) 8 | faraday (0.9.2) 9 | multipart-post (>= 1.2, < 3) 10 | git (1.3.0) 11 | github_api (0.14.5) 12 | addressable (~> 2.4.0) 13 | descendants_tracker (~> 0.0.4) 14 | faraday (~> 0.8, < 0.10) 15 | hashie (>= 3.4) 16 | oauth2 (~> 1.0) 17 | github_changelog_generator (1.13.1) 18 | colorize (~> 0.7) 19 | github_api (~> 0.12) 20 | rake (>= 10.0) 21 | hashie (3.4.4) 22 | jwt (1.5.4) 23 | multi_json (1.12.1) 24 | multi_xml (0.5.5) 25 | multipart-post (2.0.0) 26 | oauth2 (1.2.0) 27 | faraday (>= 0.8, < 0.10) 28 | jwt (~> 1.0) 29 | multi_json (~> 1.3) 30 | multi_xml (~> 0.5) 31 | rack (>= 1.2, < 3) 32 | rack (2.0.1) 33 | rake (11.2.2) 34 | thread_safe (0.3.5) 35 | version (1.0.0) 36 | 37 | PLATFORMS 38 | ruby 39 | 40 | DEPENDENCIES 41 | git (~> 1.3.0) 42 | github_changelog_generator (~> 1.13.1) 43 | rake (~> 11.2.2) 44 | version (~> 1.0.0) 45 | 46 | BUNDLED WITH 47 | 1.12.1 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------- settings 2 | FIND := find 3 | DIR := examples 4 | CRITERIA := \( -name "*.ino" -o -name "*.pde" \) 5 | EACH_EXAMPLE := $(FIND) $(DIR) $(CRITERIA) -exec 6 | BUILD := platformio ci 7 | LIB := src 8 | 9 | #--------------------------------------------------------------------- targets 10 | # update .travis.yml if target boards added 11 | all: uno due huzzah genuino101 teensy31 12 | 13 | uno due huzzah genuino101 teensy31: 14 | PLATFORMIO_BOARD=$@ $(MAKE) build 15 | 16 | build: 17 | $(EACH_EXAMPLE) $(BUILD) --board=$(PLATFORMIO_BOARD) --lib=$(LIB) {} \; 18 | 19 | .PHONY: all uno due huzzah genuino101 teensy31 build 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModbusMaster 2 | [![GitHub release](https://img.shields.io/github/release/4-20ma/ModbusMaster.svg?maxAge=3600)][GitHub release] 3 | [![Travis](https://img.shields.io/travis/4-20ma/ModbusMaster.svg?maxAge=3600)][Travis] 4 | [![license](https://img.shields.io/github/license/4-20ma/ModbusMaster.svg?maxAge=3600)][license] 5 | [![code of conduct](https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg?maxAge=3600)][code of conduct] 6 | 7 | [GitHub release]: https://github.com/4-20ma/ModbusMaster 8 | [Travis]: https://travis-ci.org/4-20ma/ModbusMaster 9 | [license]: LICENSE 10 | [code of conduct]: CODE_OF_CONDUCT.md 11 | 12 | 13 | ## Overview 14 | This is an Arduino library for communicating with Modbus slaves over RS232/485 (via RTU protocol). 15 | 16 | 17 | ## Features 18 | The following Modbus functions are available: 19 | 20 | Discrete Coils/Flags 21 | 22 | - 0x01 - Read Coils 23 | - 0x02 - Read Discrete Inputs 24 | - 0x05 - Write Single Coil 25 | - 0x0F - Write Multiple Coils 26 | 27 | Registers 28 | 29 | - 0x03 - Read Holding Registers 30 | - 0x04 - Read Input Registers 31 | - 0x06 - Write Single Register 32 | - 0x10 - Write Multiple Registers 33 | - 0x16 - Mask Write Register 34 | - 0x17 - Read Write Multiple Registers 35 | 36 | Both full-duplex and half-duplex RS232/485 transceivers are supported. Callback functions are provided to toggle Data Enable (DE) and Receiver Enable (/RE) pins. 37 | 38 | 39 | ## Installation 40 | 41 | #### Library Manager 42 | Install the library into your Arduino IDE using the Library Manager (available from IDE version 1.6.2). Open the IDE and click Sketch > Include Library > Manage Libraries… 43 | 44 | Scroll or search for `ModbusMaster`, then select the version of the library you want to install. Quit/re-launch the IDE to refresh the list; new versions are automatically added to the list, once released on GitHub. 45 | 46 | Refer to Arduino Tutorials > Libraries [Using the Library Manager](https://www.arduino.cc/en/Guide/Libraries#toc3). 47 | 48 | #### Zip Library 49 | Refer to Arduino Tutorials > Libraries [Importing a .zip Library](https://www.arduino.cc/en/Guide/Libraries#toc4). 50 | 51 | #### Manual 52 | Refer to Arduino Tutorials > Libraries [Manual Installation](https://www.arduino.cc/en/Guide/Libraries#toc5). 53 | 54 | 55 | ## Hardware 56 | This library has been tested with an Arduino [Duemilanove](http://www.arduino.cc/en/Main/ArduinoBoardDuemilanove), PHOENIX CONTACT [nanoLine](https://www.phoenixcontact.com/online/portal/us?1dmy&urile=wcm%3apath%3a/usen/web/main/products/subcategory_pages/standard_logic_modules_p-21-03-03/3329dd38-7c6a-46e1-8260-b9208235d6fe/3329dd38-7c6a-46e1-8260-b9208235d6fe) controller, connected via RS485 using a Maxim [MAX488EPA](http://www.maxim-ic.com/quick_view2.cfm/qv_pk/1111) transceiver. 57 | 58 | 59 | ## Caveats 60 | Conforms to Arduino IDE 1.5 Library Specification v2.1 which requires Arduino IDE >= 1.5. 61 | 62 | Arduinos prior to the Mega have one serial port which must be connected to USB (FTDI) for uploading sketches and to the RS232/485 device/network for running sketches. You will need to disconnect pin 0 (RX) while uploading sketches. After a successful upload, you can reconnect pin 0. 63 | 64 | 65 | ## Support 66 | Please [submit an issue](https://github.com/4-20ma/ModbusMaster/issues) for all questions, bug reports, and feature requests. Email requests will be politely redirected to the issue tracker so others may contribute to the discussion and requestors get a more timely response. 67 | 68 | 69 | ## Example 70 | The library contains a few sketches that demonstrate use of the `ModbusMaster` library. You can find these in the [examples](https://github.com/4-20ma/ModbusMaster/tree/master/examples) folder. 71 | 72 | ``` cpp 73 | /* 74 | 75 | Basic.pde - example using ModbusMaster library 76 | 77 | Library:: ModbusMaster 78 | Author:: Doc Walker <4-20ma@wvfans.net> 79 | 80 | Copyright:: 2009-2016 Doc Walker 81 | 82 | Licensed under the Apache License, Version 2.0 (the "License"); 83 | you may not use this file except in compliance with the License. 84 | You may obtain a copy of the License at 85 | 86 | http://www.apache.org/licenses/LICENSE-2.0 87 | 88 | Unless required by applicable law or agreed to in writing, software 89 | distributed under the License is distributed on an "AS IS" BASIS, 90 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 91 | See the License for the specific language governing permissions and 92 | limitations under the License. 93 | 94 | */ 95 | 96 | #include 97 | 98 | 99 | // instantiate ModbusMaster object 100 | ModbusMaster node; 101 | 102 | 103 | void setup() 104 | { 105 | // use Serial (port 0); initialize Modbus communication baud rate 106 | Serial.begin(19200); 107 | 108 | // communicate with Modbus slave ID 2 over Serial (port 0) 109 | node.begin(2, Serial); 110 | } 111 | 112 | 113 | void loop() 114 | { 115 | static uint32_t i; 116 | uint8_t j, result; 117 | uint16_t data[6]; 118 | 119 | i++; 120 | 121 | // set word 0 of TX buffer to least-significant word of counter (bits 15..0) 122 | node.setTransmitBuffer(0, lowWord(i)); 123 | 124 | // set word 1 of TX buffer to most-significant word of counter (bits 31..16) 125 | node.setTransmitBuffer(1, highWord(i)); 126 | 127 | // slave: write TX buffer to (2) 16-bit registers starting at register 0 128 | result = node.writeMultipleRegisters(0, 2); 129 | 130 | // slave: read (6) 16-bit registers starting at register 2 to RX buffer 131 | result = node.readHoldingRegisters(2, 6); 132 | 133 | // do something with data if read is successful 134 | if (result == node.ku8MBSuccess) 135 | { 136 | for (j = 0; j < 6; j++) 137 | { 138 | data[j] = node.getResponseBuffer(j); 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | _Project inspired by [Arduino Modbus Master](http://sites.google.com/site/jpmzometa/arduino-mbrt/arduino-modbus-master)._ 145 | 146 | 147 | ## License & Authors 148 | 149 | - Author:: Doc Walker ([4-20ma@wvfans.net](mailto:4-20ma@wvfans.net)) 150 | - Author:: Ag Primatic ([agprimatic@gmail.com](mailto:agprimatic@gmail.com)) 151 | - Author:: Marius Kintel ([marius@kintel.net](mailto:marius@kintel.net)) 152 | 153 | ``` 154 | Copyright:: 2009-2016 Doc Walker 155 | 156 | Licensed under the Apache License, Version 2.0 (the "License"); 157 | you may not use this file except in compliance with the License. 158 | You may obtain a copy of the License at 159 | 160 | http://www.apache.org/licenses/LICENSE-2.0 161 | 162 | Unless required by applicable law or agreed to in writing, software 163 | distributed under the License is distributed on an "AS IS" BASIS, 164 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 165 | See the License for the specific language governing permissions and 166 | limitations under the License. 167 | ``` 168 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Copyright:: 2009-2016 Doc Walker 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require 'git' 19 | require 'github_changelog_generator/task' 20 | require 'rake' 21 | require 'rubygems' 22 | require 'rake/version_task' # gem install version 23 | require 'version' 24 | 25 | # requires additional packages on MacOS (including Homebrew): 26 | # $ /usr/bin/ruby -e "$(curl -fsSL \ 27 | # https://raw.githubusercontent.com/Homebrew/install/master/install)" 28 | # $ brew install doxygen # generates documentation from source code 29 | # $ brew cask install mactex # MacTeX 30 | 31 | Rake::VersionTask.new do |task| 32 | # prevent auto-commit on version bump 33 | task.with_git = false 34 | end 35 | 36 | # adjust as appropriate 37 | CWD = File.expand_path(File.dirname(__FILE__)) 38 | DOXYFILE = 'Doxyfile' 39 | GITHUB_USERNAME = '4-20ma' 40 | GITHUB_REPO = 'ModbusMaster' 41 | HEADER_FILE = "#{GITHUB_REPO}.h" 42 | CHANGELOG_FILE = 'CHANGELOG.md' 43 | PROPERTIES_FILE = 'library.properties' 44 | VERSION_FILE = Version.version_file('').basename.to_s 45 | 46 | 47 | task :default => :info 48 | 49 | desc 'Display instructions for public release' 50 | task :info do 51 | puts <<-EOF.gsub(/^\s{2}/, '') 52 | 53 | Instructions for public release 54 | 55 | - Update version, as appropriate: 56 | 57 | $ rake version:bump # or 58 | $ rake version:bump:minor # or 59 | $ rake version:bump:major # or 60 | edit 'VERSION' file directly 61 | 62 | - Prepare release date, 'CHANGELOG.md' file, documentation: 63 | 64 | $ rake prepare 65 | 66 | - Review changes to 'CHANGELOG.md' file 67 | This file is assembled using git commit messages; review for completeness. 68 | 69 | - Review html documentation files 70 | These files are assembled using source code Doxygen tags; review for 71 | for completeness. 72 | 73 | - Add & commit source files, tag, push to origin/master; 74 | add & commit documentation files, push to origin/gh-pages: 75 | 76 | $ rake release 77 | 78 | EOF 79 | end # task :info 80 | 81 | 82 | desc "Prepare #{CHANGELOG_FILE} for release" 83 | task :prepare => 'prepare:default' 84 | 85 | namespace :prepare do 86 | task :default => [ 87 | :release_date, 88 | :library_properties, 89 | :changelog, 90 | :documentation 91 | ] 92 | 93 | desc 'Prepare documentation' 94 | task :documentation do 95 | version = Version.current.to_s 96 | 97 | # update parameters in Doxyfile 98 | file = File.join(CWD, 'doc', DOXYFILE) 99 | 100 | contents = IO.read(file) 101 | contents.sub!(/(^PROJECT_NUMBER\s*=)(.*)$/) do |match| 102 | "#{$1} v#{version}" 103 | end # contents.sub!(...) 104 | IO.write(file, contents) 105 | 106 | # chdir to doc/ and call doxygen to update documentation 107 | Dir.chdir(to = File.join(CWD, 'doc')) 108 | system('doxygen', DOXYFILE) 109 | 110 | # chdir to doc/latex and call doxygen to update documentation 111 | Dir.chdir(from = File.join(CWD, 'doc', 'latex')) 112 | system('make') 113 | 114 | # move/rename file to 'extras/GITHUB_REPO reference-x.y.pdf' 115 | to = File.join(CWD, 'extras') 116 | FileUtils.mv(File.join(from, 'refman.pdf'), 117 | File.join(to, "#{GITHUB_REPO} reference-#{version}.pdf")) 118 | end # task :documentation 119 | 120 | desc 'Prepare release history' 121 | GitHubChangelogGenerator::RakeTask.new(:changelog) do |config| 122 | config.add_issues_wo_labels = false 123 | config.add_pr_wo_labels = false 124 | config.enhancement_labels = [ 125 | 'Type: Enhancement' 126 | ] 127 | config.bug_labels = ['Type: Bug'] 128 | config.exclude_labels = ['Type: Question'] 129 | config.header = '# ModbusMaster CHANGELOG' 130 | config.include_labels = [ 131 | 'Type: Bug', 132 | 'Type: Enhancement', 133 | 'Type: Feature Request', 134 | 'Type: Maintenance' 135 | ] 136 | # config.since_tag = '0.1.0' 137 | config.future_release = "v#{Version.current.to_s}" 138 | config.user = GITHUB_USERNAME 139 | config.project = GITHUB_REPO 140 | end # GitHubChangelogGenerator::RakeTask.new 141 | 142 | desc 'Update version in library properties file' 143 | task :library_properties do 144 | version = Version.current.to_s 145 | 146 | file = File.join(CWD, PROPERTIES_FILE) 147 | 148 | contents = IO.read(file) 149 | contents.sub!(/(version=\s*)(.*)$/) do |match| 150 | "#{$1}#{version}" 151 | end # contents.sub!(...) 152 | IO.write(file, contents) 153 | end # task :library_properties 154 | 155 | desc 'Update release date in header file' 156 | task :release_date do 157 | file = File.join(CWD, 'src', HEADER_FILE) 158 | 159 | contents = IO.read(file) 160 | contents.sub!(/(\\date\s*)(.*)$/) do |match| 161 | "#{$1}#{Time.now.strftime('%-d %b %Y')}" 162 | end # contents.sub!(...) 163 | IO.write(file, contents) 164 | end # task :release_date 165 | 166 | end # namespace :prepare 167 | 168 | 169 | desc 'Release source & documentation' 170 | task :release => 'release:default' 171 | 172 | namespace :release do 173 | task :default => [:source, :documentation] 174 | 175 | desc 'Commit documentation changes related to version bump' 176 | task :documentation do 177 | version = Version.current.to_s 178 | cwd = File.expand_path(File.join(File.dirname(__FILE__), 'doc', 'html')) 179 | g = Git.open(cwd) 180 | 181 | # `git add .` 182 | g.add 183 | 184 | # remove each deleted item 185 | g.status.deleted.each do |item| 186 | g.remove(item[0]) 187 | end # g.status.deleted.each 188 | 189 | # commit changes if items added, changed, or deleted 190 | if g.status.added.size > 0 || g.status.changed.size > 0 || 191 | g.status.deleted.size > 0 then 192 | message = "Update documentation for v#{version}" 193 | puts g.commit(message) 194 | else 195 | puts "No changes to commit v#{version}" 196 | end # if g.status.added.size > 0 || g.status.changed.size > 0... 197 | 198 | g.push('origin', 'gh-pages') 199 | end # task :documentation 200 | 201 | desc 'Commit source changes related to version bump' 202 | task :source do 203 | version = Version.current.to_s 204 | `git add \ 205 | doc/#{DOXYFILE} \ 206 | "extras/#{GITHUB_REPO} reference-#{version}.pdf" \ 207 | src/#{HEADER_FILE} \ 208 | #{CHANGELOG_FILE} \ 209 | #{PROPERTIES_FILE} \ 210 | #{VERSION_FILE} \ 211 | ` 212 | `git commit -m 'Version bump to v#{version}'` 213 | `git tag -a -f -m 'Version v#{version}' v#{version}` 214 | `git push origin master` 215 | `git push --tags` 216 | end # task :source 217 | 218 | end # namespace :release 219 | -------------------------------------------------------------------------------- /STYLE.md: -------------------------------------------------------------------------------- 1 | ModbusMaster Style Guide 2 | ======================== 3 | 4 | The following references provide sound guidance for writing C/C++ code for the Arduino platform. 5 | 6 | - [Arduino API Style Guide (AASG)](http://www.arduino.cc/en/Reference/APIStyleGuide) 7 | - [Bjarne Stroustrup's C++ Style Guide](http://www.stroustrup.com/bs_faq2.html) 8 | - [JSF Air Vehicle C++ Coding Standards (JSFAV)](http://www.stroustrup.com/JSF-AV-rules.pdf) 9 | 10 | Opinions about style and generally accepted usage patterns may vary widely. I've carefully chosen a few key items to emphasize and enforce for this library in order to promote readability, usability, and safe coding practices. **Pull requests will follow these guidelines in order to be considered**. 11 | 12 | 13 | General \[AASG\] 14 | ------- 15 | 16 | Use the established Arduino core libraries and styles. 17 | 18 | - Use `read()` to read inputs, and `write()` to write to outputs, e.g. `digitalRead()`, `analogWrite()`, etc. 19 | - Use the `Stream.h` and `Print.h` libraries when dealing with byte streams. If it’s not appropriate, at least try to use its API as a model. For more on this, see below. 20 | - For network applications, use the Client and Server libraries as the basis. 21 | - Use `begin()` to initialize a library instance, usually with some settings. Use `end()` to stop it. 22 | - Use camelCase function names, not underscore. For example, `analogRead`, not `analog_read`. Or `myNewFunction`, not `my_new_function`. We've adopted this from Processing.org for readability's sake. Refer to AV Rule 45 and AV Rule 51. 23 | - When using serial communication, allow the user to specify any `Stream` object, rather than hard-coding `Serial`. This will make the library compatible with all serial ports on Mega and the Due, and can also use alternate interfaces like `SoftwareSerial`. The `Stream` object can be passed to the library's constructor or to a `begin()` function (as a reference, not a pointer). See Firmata 2.3 or XBee 0.4 for examples of each approach. 24 | 25 | 26 | Rules \[JSFAV 4.2\] 27 | ----- 28 | 29 | #### Should, Will, and Shall Rules 30 | 31 | There are three types of rules: **should**, **will**, and **shall** rules. Each rule contains either a **"should"**, **"will"** or a **"shall"** in bold letters indicating its type. 32 | 33 | - **Should** rules are advisory rules. They strongly suggest the recommended way of doing things. 34 | - **Will** rules are intended to be mandatory requirements. It is expected that they will be followed, but they do not require verification. They are limited to non-safety-critical requirements that cannot be easily verified (e.g., naming conventions). 35 | - **Shall** rules are mandatory requirements. They must be followed and they require verification (either automatic or manual). 36 | 37 | 38 | Pre-Processing Directives \[JSFAV 4.6\] 39 | ------------------------- 40 | Since the pre-processor knows nothing about C++, it should not be used to do what can otherwise be done in C++. 41 | 42 | - AV Rule 26 43 | 44 | Only the following pre-processor directives shall be used: 45 | 46 | 1. `#ifndef` 47 | 1. `#define` 48 | 1. `#endif` 49 | 1. `#include` 50 | 51 | **Rationale**: Limit the use of the pre-processor to those cases where it is necessary. 52 | 53 | #### \#ifndef and \#endif Pre-Processing Directives 54 | 55 | - AV Rule 27 56 | 57 | `#ifndef`, `#define` and `#endif` **will** be used to prevent multiple inclusions of the same header file. Other techniques to prevent the multiple inclusions of header files **will not** be used. 58 | 59 | **Rationale**: Eliminate multiple inclusions of the same header file in a standard way. 60 | 61 | - AV Rule 28 62 | 63 | The `#ifndef` and `#endif` pre-processor directives **will** only be used as defined in AV Rule 27 to prevent multiple inclusions of the same header file. 64 | 65 | **Rationale**: Conditional code compilation should be kept to a minimum as it can significantly obscure testing and maintenance efforts. 66 | 67 | #### \#define Pre-Processing Directive 68 | 69 | - AV Rule 29 70 | 71 | The `#define` pre-processor directive **shall not** be used to create inline macros. Inline functions **shall** be used instead. 72 | 73 | **Rationale**: Inline functions do not require text substitutions and behave well when called with arguments (e.g. type checking is performed). 74 | 75 | - AV Rule 30 76 | 77 | The `#define` pre-processor directive **shall not** be used to define constant values. Instead, the `const` qualifier shall be applied to variable declarations to specify constant values. 78 | 79 | **Rationale**: `const` variables follow scope rules, are subject to type checking and do not require text substitutions (which can be confusing or misleading). 80 | 81 | - AV Rule 31 82 | 83 | The `#define` pre-processor directive **will** only be used as part of the technique to prevent multiple inclusions of the same header file. 84 | 85 | **Rationale**: `#define` can be used to specify conditional compilation (AV Rule 27 and AV Rule 28), inline macros (AV Rule 29) and constants (AV Rule 30). This rule specifies that the only allowable use of `#define` is to prevent multiple includes of the same header file (AV Rule 27). 86 | 87 | #### \#include Pre-Processing Directive 88 | 89 | - AV Rule 32 90 | 91 | The `#include` pre-processor directive **will** only be used to include header (\*.h) files. 92 | 93 | **Rationale**: Clarity. The only files included in a .cpp file should be the relevant header (\*.h) files. 94 | 95 | 96 | Header Files \[JSFAV 4.7\] 97 | ------------ 98 | 99 | - AV Rule 33 100 | 101 | The `#include` directive **shall** use the `` notation to include header files. 102 | 103 | **Rationale**: The include form `"filename.h"` is typically used to include local header files. However, due to the unfortunate divergence in vendor implementations, only the `` form will be used. 104 | 105 | - AV Rule 35 106 | 107 | A header file **will** contain a mechanism that prevents multiple inclusions of itself. 108 | 109 | **Rationale**: Avoid accidental header file recursion. Note AV Rule 27 specifies the mechanism by which multiple inclusions are to be eliminated whereas this rule (AV Rule 35) specifies that each header file must use that mechanism. 110 | 111 | - AV Rule 37 112 | 113 | Header (include) files **should** include only those header files that are required for them to successfully compile. Files that are only used by the associated .cpp file should be placed in the .cpp file—not the .h file. 114 | 115 | **Rationale**: The `#include` statements in a header file define the dependencies of the file. Fewer dependencies imply looser couplings and hence a smaller ripple-effect when the header file is required to change. 116 | 117 | 118 | Style \[JSFAV 4.9\] 119 | ----- 120 | Imposing constraints on the format of syntactic elements makes source code easier to read due to consistency in form and appearance. 121 | 122 | - AV Rule 41 (modified) 123 | 124 | Source lines **will** be kept to a length of 78 characters or less. 125 | 126 | **Rationale**: Readability and style. Very long source lines can be difficult to read and understand. 127 | 128 | - AV Rule 42 129 | 130 | Each expression-statement **will** be on a separate line. 131 | 132 | **Rationale**: Simplicity, readability, and style. 133 | 134 | - AV Rule 43 (modified) 135 | 136 | Tabs **will** be avoided. 137 | 138 | **Rationale**: Tabs are interpreted differently across various editors and printers. 139 | 140 | - AV Rule 44 141 | 142 | All indentations **will** be at least two spaces and be consistent within the same source file. 143 | 144 | **Rationale**: Readability and style. 145 | 146 | #### Naming Identifiers 147 | 148 | The choice of identifier names should: 149 | 150 | - Suggest the usage of the identifier. 151 | - Consist of a descriptive name that is short yet meaningful. 152 | - Be long enough to avoid name conflicts, but not excessive in length. 153 | - Include abbreviations that are generally accepted. 154 | 155 | Note: In general, the above guidelines should be followed. However, conventional usage of simple identifiers (i, x, y, p, etc.) in small scopes can lead to cleaner code and will therefore be permitted. 156 | 157 | Additionally, the term ‘word’ in the following naming convention rules may be used to refer to a word, an acronym, an abbreviation, or a number. 158 | 159 | - AV Rule 45 160 | 161 | All words in an identifier **will** be separated by the '\_' character. 162 | 163 | **Exception**: Function names follow the camelCase convention according to the Arduino API Style Guide. Refer to AV Rule 51. 164 | 165 | **Rationale**: Readability and Style. 166 | 167 | - AV Rule 47 (modified) 168 | 169 | Identifiers **should not** begin with the underscore character '\_'. 170 | 171 | **Exception**: Currently, private members of the `ModbusMaster` class begin with '\_'. 172 | 173 | **Rationale**: '\_' is often used as the first character in the name of library functions (e.g. `_main`, `_exit`, etc.) In order to avoid name collisions, identifiers should not begin with '\_'. 174 | 175 | - AV Rule 49 176 | 177 | All acronyms in an identifier **will** be composed of uppercase letters. 178 | 179 | Note: An acronym will always be in uppercase, even if the acronym is located in a portion of an identifier that is specified to be lowercase by other rules. 180 | 181 | **Rationale**: Readability. 182 | 183 | - AV Rule 50 184 | 185 | The first word of the name of a class, structure, namespace, enumeration, or type created with typedef **will** begin with an uppercase letter. All others letters will be lowercase. 186 | 187 | **Rationale**: Style. 188 | 189 | **Exception**: The first letter of a typedef name may be in lowercase in order to conform to a standard library interface or when used as a replacement for fundamental types. 190 | 191 | - AV Rule 51 (modified) 192 | 193 | All letters contained in *function* names **will** be composed of a mix of lowercase and uppercase letters in camelCase format (e.g. first letter of first word is lowercase and first letter of each subsequent word is uppercase). This is to maintain consistency with the Arduion API Style Guide. Refer to AV Rule 45. 194 | 195 | All letters contained in *variable* names **will** be composed entirely of lowercase letters. 196 | 197 | **Rationale**: Style. 198 | 199 | - AV Rule 52 200 | 201 | Identifiers for constant and enumerator values **shall** be lowercase. 202 | 203 | **Rationale**: Although it is an accepted convention to use uppercase letters for constants and enumerators, it is possible for third party libraries to replace constant/enumerator names as part of the macro substitution process (macros are also typically represented with uppercase letters). 204 | 205 | #### Classes 206 | 207 | - AV Rule 57 208 | 209 | The public, protected, and private sections of a class **will** be declared in that order (the public section is declared before the protected section which is declared before the private section). 210 | 211 | **Rationale**: By placing the public section first, everything that is of interest to a user is gathered in the beginning of the class definition. The protected section may be of interest to designers when considering inheriting from the class. The private section contains details that should be of the least general interest. 212 | 213 | #### Functions 214 | 215 | - AV Rule 58 216 | 217 | When declaring and defining functions with more than two parameters, the leading parenthesis and the first argument **will** be written on the same line as the function name. Each additional argument will be written on a separate line (with the closing parenthesis directly after the last argument). 218 | 219 | **Rationale**: Readability and style. 220 | 221 | #### Blocks 222 | 223 | - AV Rule 59 224 | 225 | The statements forming the body of an `if`, `else if`, `else`, `while`, `do...while` or `for` statement **shall** always be enclosed in braces, even if the braces form an empty block. 226 | 227 | **Rationale**: Readability. It can be difficult to see ';' when it appears by itself. 228 | 229 | - AV Rule 60 230 | 231 | Braces ('{}') which enclose a block will be placed in the same column, on separate lines directly before and after the block. 232 | 233 | **Example**: 234 | ``` 235 | if (var_name == true) 236 | { 237 | } 238 | else 239 | { 240 | } 241 | ``` 242 | 243 | - AV Rule 61 244 | 245 | Braces ('{}') which enclose a block **will** have nothing else on the line except comments (if necessary). 246 | 247 | #### Pointers and References 248 | 249 | - AV Rule 62 250 | 251 | The dereference operator '\*' and the address-of operator '&' **will** be directly connected with the type-specifier. 252 | 253 | **Rationale**: The `int32* p;` form emphasizes type over syntax while the `int32 *p;` form emphasizes syntax over type. Although both forms are equally valid C++, the heavy emphasis on types in C++ suggests that `int32* p;` is the preferable form. 254 | 255 | **Examples**: 256 | ``` 257 | int32* p; // correct 258 | int32 *p; // incorrect 259 | int32* p, q; // probable error 260 | ``` 261 | 262 | #### Miscellaneous 263 | 264 | - AV Rule 63 265 | 266 | Spaces **will not** be used around '.' or '->', nor between unary operators and operands. 267 | 268 | **Rationale**: Readability and style. 269 | 270 | 271 | Classes \[JSFAV 4.10\] 272 | ------- 273 | 274 | #### const Member Functions 275 | 276 | - AV Rule 69 277 | 278 | A member function that does not affect the state of an object (its instance variables) **will** be declared `const`. 279 | 280 | Member functions should be `const` by default. Only when there is a clear, explicit reason should the `const` modifier on member functions be omitted. 281 | 282 | **Rationale**: Declaring a member function `const` is a means of ensuring that objects will not be modified when they should not. Furthermore, C++ allows member functions to be overloaded on their `const`-ness. 283 | 284 | 285 | Initialization \[JSFAV 4.16\] 286 | -------------- 287 | 288 | - AV Rule 142 289 | 290 | All variables **shall** be initialized before use. 291 | 292 | **Rationale**: Prevent the use of variables before they have been properly initialized. 293 | 294 | - AV Rule 143 295 | 296 | Variables **will not** be introduced until they can be initialized with meaningful values. 297 | 298 | **Rationale**: Prevent clients from accessing variables without meaningful values. 299 | 300 | 301 | Constants \[JSFAV 4.18\] 302 | --------- 303 | 304 | - AV Rule 149 305 | 306 | Octal constants (other than zero) **shall not** be used. 307 | 308 | **Rationale**: Any integer constant beginning with a zero ('0') is defined by the C++ standard to be an octal constant. Due to the confusion this causes, octal constants should be avoided. 309 | 310 | Note: Hexadecimal numbers and zero (which is also an octal constant) are allowed. 311 | 312 | - AV Rule 150 313 | 314 | Hexadecimal constants **will** be represented using all uppercase letters. 315 | 316 | 317 | Variables \[JSFAV 4.19\] 318 | --------- 319 | 320 | - AV Rule 152 321 | 322 | Multiple variable declarations **shall not** be allowed on the same line. 323 | 324 | **Rationale**: Increases readability and prevents confusion (see also AV Rule 62). 325 | 326 | **Examples**: 327 | ``` 328 | int32 p; // correct 329 | int32 q; // correct 330 | int32 p, q; // probable error 331 | int32 first_button_on_the_left_box, i; // incorrect; easy to overlook i 332 | ``` 333 | 334 | 335 | Flow Control Structures \[JSFAV 4.24\] 336 | ----------------------- 337 | 338 | - AV Rule 188 339 | 340 | Labels **will** not be used, except in switch statements. 341 | 342 | **Rationale**: Labels are typically either used in switch statements or are as the targets for goto statements. See exception given in AV Rule 189. 343 | 344 | - AV Rule 189 345 | 346 | The `goto` statement **shall not** be used. 347 | 348 | **Rationale**: Frequent use of the `goto` statement tends to lead to code that is both difficult to read and maintain. 349 | 350 | - AV Rule 190 351 | 352 | The `continue` statement **shall not** be used. 353 | 354 | - AV Rule 191 355 | 356 | The `break` statement **shall not** be used (except to terminate the cases of a switch statement). 357 | 358 | **Exception**: The `break` statement may be used to "break" out of a single loop provided the alternative would obscure or otherwise significantly complicate the control logic. 359 | 360 | - AV Rule 192 361 | 362 | All `if`, `else if` constructs **will** contain either a final `else` clause or a comment indicating why a final `else` clause is not necessary. 363 | 364 | **Rationale**: Provide a defensive strategy to ensure that all cases are handled by an `else if` series. 365 | 366 | Note: This rule only applies when an `if` statement is followed by one or more `else if`’s. 367 | 368 | - AV Rule 193 369 | 370 | Every non-empty `case` clause in a `switch` statement **shall** be terminated with a `break` statement. 371 | 372 | **Rationale**: Eliminates potentially confusing behavior since execution will fall through to the code of the next `case` clause if a `break` statement does not terminate the previous `case` clause. 373 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.1 2 | -------------------------------------------------------------------------------- /examples/Basic/Basic.pde: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Basic.pde - example using ModbusMaster library 4 | 5 | Library:: ModbusMaster 6 | Author:: Doc Walker <4-20ma@wvfans.net> 7 | 8 | Copyright:: 2009-2016 Doc Walker 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); 11 | you may not use this file except in compliance with the License. 12 | You may obtain a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, 18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | See the License for the specific language governing permissions and 20 | limitations under the License. 21 | 22 | */ 23 | 24 | #include 25 | 26 | 27 | // instantiate ModbusMaster object 28 | ModbusMaster node; 29 | 30 | 31 | void setup() 32 | { 33 | // use Serial (port 0); initialize Modbus communication baud rate 34 | Serial.begin(19200); 35 | 36 | // communicate with Modbus slave ID 2 over Serial (port 0) 37 | node.begin(2, Serial); 38 | } 39 | 40 | 41 | void loop() 42 | { 43 | static uint32_t i; 44 | uint8_t j, result; 45 | uint16_t data[6]; 46 | 47 | i++; 48 | 49 | // set word 0 of TX buffer to least-significant word of counter (bits 15..0) 50 | node.setTransmitBuffer(0, lowWord(i)); 51 | 52 | // set word 1 of TX buffer to most-significant word of counter (bits 31..16) 53 | node.setTransmitBuffer(1, highWord(i)); 54 | 55 | // slave: write TX buffer to (2) 16-bit registers starting at register 0 56 | result = node.writeMultipleRegisters(0, 2); 57 | 58 | // slave: read (6) 16-bit registers starting at register 2 to RX buffer 59 | result = node.readHoldingRegisters(2, 6); 60 | 61 | // do something with data if read is successful 62 | if (result == node.ku8MBSuccess) 63 | { 64 | for (j = 0; j < 6; j++) 65 | { 66 | data[j] = node.getResponseBuffer(j); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/PhoenixContact_nanoLC/PhoenixContact_nanoLC.pde: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | PhoenixContact_nanoLC.pde - example using ModbusMaster library 4 | to communicate with PHOENIX CONTACT nanoLine controller. 5 | 6 | Library:: ModbusMaster 7 | Author:: Doc Walker <4-20ma@wvfans.net> 8 | 9 | Copyright:: 2009-2016 Doc Walker 10 | 11 | Licensed under the Apache License, Version 2.0 (the "License"); 12 | you may not use this file except in compliance with the License. 13 | You may obtain a copy of the License at 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, 19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | See the License for the specific language governing permissions and 21 | limitations under the License. 22 | 23 | */ 24 | 25 | #include 26 | 27 | // discrete coils 28 | #define NANO_DO(n) (0x0000 + n) ///< returns nanoLC discrete output address 29 | #define NANO_FLAG(n) (0x1000 + n) ///< returns nanoLC flag address 30 | 31 | // discrete inputs 32 | #define NANO_DI(n) (0x0000 + n) ///< returns nanoLC discrete input address 33 | 34 | // analog holding registers 35 | #define NANO_REG(n) (0x0000 + 2 * n) ///< returns nanoLC holding register address 36 | #define NANO_AO(n) (0x1000 + 2 * n) ///< returns nanoLC analog output address 37 | #define NANO_TCP(n) (0x2000 + 2 * n) ///< returns nanoLC timer/counter preset address 38 | #define NANO_OTP(n) (0x3000 + 2 * n) ///< returns nanoLC discrete output preset address 39 | #define NANO_HSP(n) (0x4000 + 2 * n) ///< returns nanoLC high-speed counter preset address 40 | #define NANO_TCA(n) (0x5000 + 2 * n) ///< returns nanoLC timer/counter accumulator address 41 | #define NANO_OTA(n) (0x6000 + 2 * n) ///< returns nanoLC discrete output accumulator address 42 | #define NANO_HSA(n) (0x7000 + 2 * n) ///< returns nanoLC high-speed counter accumulator address 43 | 44 | // analog input registers 45 | #define NANO_AI(n) (0x0000 + 2 * n) ///< returns nanoLC analog input address 46 | 47 | 48 | // instantiate ModbusMaster object 49 | ModbusMaster nanoLC; 50 | 51 | 52 | void setup() 53 | { 54 | // use Serial (port 0); initialize Modbus communication baud rate 55 | Serial.begin(19200); 56 | 57 | // communicate with Modbus slave ID 1 over Serial (port 0) 58 | nanoLC.begin(1, Serial); 59 | } 60 | 61 | 62 | void loop() 63 | { 64 | static uint32_t u32ShiftRegister; 65 | static uint32_t i; 66 | uint8_t u8Status; 67 | 68 | u32ShiftRegister = ((u32ShiftRegister < 0x01000000) ? (u32ShiftRegister << 4) : 1); 69 | if (u32ShiftRegister == 0) u32ShiftRegister = 1; 70 | i++; 71 | 72 | // set word 0 of TX buffer to least-significant word of u32ShiftRegister (bits 15..0) 73 | nanoLC.setTransmitBuffer(0, lowWord(u32ShiftRegister)); 74 | 75 | // set word 1 of TX buffer to most-significant word of u32ShiftRegister (bits 31..16) 76 | nanoLC.setTransmitBuffer(1, highWord(u32ShiftRegister)); 77 | 78 | // set word 2 of TX buffer to least-significant word of i (bits 15..0) 79 | nanoLC.setTransmitBuffer(2, lowWord(i)); 80 | 81 | // set word 3 of TX buffer to most-significant word of i (bits 31..16) 82 | nanoLC.setTransmitBuffer(3, highWord(i)); 83 | 84 | // write TX buffer to (4) 16-bit registers starting at NANO_REG(1) 85 | // read (4) 16-bit registers starting at NANO_REG(0) to RX buffer 86 | // data is available via nanoLC.getResponseBuffer(0..3) 87 | nanoLC.readWriteMultipleRegisters(NANO_REG(0), 4, NANO_REG(1), 4); 88 | 89 | // write lowWord(u32ShiftRegister) to single 16-bit register starting at NANO_REG(3) 90 | nanoLC.writeSingleRegister(NANO_REG(3), lowWord(u32ShiftRegister)); 91 | 92 | // write highWord(u32ShiftRegister) to single 16-bit register starting at NANO_REG(3) + 1 93 | nanoLC.writeSingleRegister(NANO_REG(3) + 1, highWord(u32ShiftRegister)); 94 | 95 | // set word 0 of TX buffer to nanoLC.getResponseBuffer(0) (bits 15..0) 96 | nanoLC.setTransmitBuffer(0, nanoLC.getResponseBuffer(0)); 97 | 98 | // set word 1 of TX buffer to nanoLC.getResponseBuffer(1) (bits 31..16) 99 | nanoLC.setTransmitBuffer(1, nanoLC.getResponseBuffer(1)); 100 | 101 | // write TX buffer to (2) 16-bit registers starting at NANO_REG(4) 102 | nanoLC.writeMultipleRegisters(NANO_REG(4), 2); 103 | 104 | // read 17 coils starting at NANO_FLAG(0) to RX buffer 105 | // bits 15..0 are available via nanoLC.getResponseBuffer(0) 106 | // bit 16 is available via zero-padded nanoLC.getResponseBuffer(1) 107 | nanoLC.readCoils(NANO_FLAG(0), 17); 108 | 109 | // read (66) 16-bit registers starting at NANO_REG(0) to RX buffer 110 | // generates Modbus exception ku8MBIllegalDataAddress (0x02) 111 | u8Status = nanoLC.readHoldingRegisters(NANO_REG(0), 66); 112 | if (u8Status == nanoLC.ku8MBIllegalDataAddress) 113 | { 114 | // read (64) 16-bit registers starting at NANO_REG(0) to RX buffer 115 | // data is available via nanoLC.getResponseBuffer(0..63) 116 | u8Status = nanoLC.readHoldingRegisters(NANO_REG(0), 64); 117 | } 118 | 119 | // read (8) 16-bit registers starting at NANO_AO(0) to RX buffer 120 | // data is available via nanoLC.getResponseBuffer(0..7) 121 | nanoLC.readHoldingRegisters(NANO_AO(0), 8); 122 | 123 | // read (64) 16-bit registers starting at NANO_TCP(0) to RX buffer 124 | // data is available via nanoLC.getResponseBuffer(0..63) 125 | nanoLC.readHoldingRegisters(NANO_TCP(0), 64); 126 | 127 | // read (64) 16-bit registers starting at NANO_OTP(0) to RX buffer 128 | // data is available via nanoLC.getResponseBuffer(0..63) 129 | nanoLC.readHoldingRegisters(NANO_OTP(0), 64); 130 | 131 | // read (64) 16-bit registers starting at NANO_TCA(0) to RX buffer 132 | // data is available via nanoLC.getResponseBuffer(0..63) 133 | nanoLC.readHoldingRegisters(NANO_TCA(0), 64); 134 | 135 | // read (64) 16-bit registers starting at NANO_OTA(0) to RX buffer 136 | // data is available via nanoLC.getResponseBuffer(0..63) 137 | nanoLC.readHoldingRegisters(NANO_OTA(0), 64); 138 | 139 | // read (8) 16-bit registers starting at NANO_AI(0) to RX buffer 140 | // data is available via nanoLC.getResponseBuffer(0..7) 141 | nanoLC.readInputRegisters(NANO_AI(0), 8); 142 | } 143 | 144 | -------------------------------------------------------------------------------- /examples/RS485_HalfDuplex/RS485_HalfDuplex.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | RS485_HalfDuplex.pde - example using ModbusMaster library to communicate 4 | with EPSolar LS2024B controller using a half-duplex RS485 transceiver. 5 | 6 | This example is tested against an EPSolar LS2024B solar charge controller. 7 | See here for protocol specs: 8 | http://www.solar-elektro.cz/data/dokumenty/1733_modbus_protocol.pdf 9 | 10 | Library:: ModbusMaster 11 | Author:: Marius Kintel 12 | 13 | Copyright:: 2009-2016 Doc Walker 14 | 15 | Licensed under the Apache License, Version 2.0 (the "License"); 16 | you may not use this file except in compliance with the License. 17 | You may obtain a copy of the License at 18 | 19 | http://www.apache.org/licenses/LICENSE-2.0 20 | 21 | Unless required by applicable law or agreed to in writing, software 22 | distributed under the License is distributed on an "AS IS" BASIS, 23 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | See the License for the specific language governing permissions and 25 | limitations under the License. 26 | 27 | */ 28 | 29 | #include 30 | 31 | /*! 32 | We're using a MAX485-compatible RS485 Transceiver. 33 | Rx/Tx is hooked up to the hardware serial port at 'Serial'. 34 | The Data Enable and Receiver Enable pins are hooked up as follows: 35 | */ 36 | #define MAX485_DE 3 37 | #define MAX485_RE_NEG 2 38 | 39 | // instantiate ModbusMaster object 40 | ModbusMaster node; 41 | 42 | void preTransmission() 43 | { 44 | digitalWrite(MAX485_RE_NEG, 1); 45 | digitalWrite(MAX485_DE, 1); 46 | } 47 | 48 | void postTransmission() 49 | { 50 | digitalWrite(MAX485_RE_NEG, 0); 51 | digitalWrite(MAX485_DE, 0); 52 | } 53 | 54 | void setup() 55 | { 56 | pinMode(MAX485_RE_NEG, OUTPUT); 57 | pinMode(MAX485_DE, OUTPUT); 58 | // Init in receive mode 59 | digitalWrite(MAX485_RE_NEG, 0); 60 | digitalWrite(MAX485_DE, 0); 61 | 62 | // Modbus communication runs at 115200 baud 63 | Serial.begin(115200); 64 | 65 | // Modbus slave ID 1 66 | node.begin(1, Serial); 67 | // Callbacks allow us to configure the RS485 transceiver correctly 68 | node.preTransmission(preTransmission); 69 | node.postTransmission(postTransmission); 70 | } 71 | 72 | bool state = true; 73 | 74 | void loop() 75 | { 76 | uint8_t result; 77 | uint16_t data[6]; 78 | 79 | // Toggle the coil at address 0x0002 (Manual Load Control) 80 | result = node.writeSingleCoil(0x0002, state); 81 | state = !state; 82 | 83 | // Read 16 registers starting at 0x3100) 84 | result = node.readInputRegisters(0x3100, 16); 85 | if (result == node.ku8MBSuccess) 86 | { 87 | Serial.print("Vbatt: "); 88 | Serial.println(node.getResponseBuffer(0x04)/100.0f); 89 | Serial.print("Vload: "); 90 | Serial.println(node.getResponseBuffer(0xC0)/100.0f); 91 | Serial.print("Pload: "); 92 | Serial.println((node.getResponseBuffer(0x0D) + 93 | node.getResponseBuffer(0x0E) << 16)/100.0f); 94 | } 95 | 96 | delay(1000); 97 | } 98 | 99 | -------------------------------------------------------------------------------- /extras/ModbusMaster reference-2.0.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4-20ma/ModbusMaster/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/extras/ModbusMaster reference-2.0.1.pdf -------------------------------------------------------------------------------- /extras/README.txt: -------------------------------------------------------------------------------- 1 | Documentation is available at: 2 | http://4-20ma.github.com/ModbusMaster 3 | 4 | Alternatively, you can download the HTML files at: 5 | [tarball] https://github.com/4-20ma/ModbusMaster/tarball/gh-pages 6 | [zipball] https://github.com/4-20ma/ModbusMaster/zipball/gh-pages 7 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For ModbusMaster 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | ModbusMaster KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | lowWord KEYWORD2 16 | highWord KEYWORD2 17 | LONG KEYWORD2 18 | 19 | begin KEYWORD2 20 | 21 | getResponseBuffer KEYWORD2 22 | clearResponseBuffer KEYWORD2 23 | setTransmitBuffer KEYWORD2 24 | clearTransmitBuffer KEYWORD2 25 | 26 | readCoils KEYWORD2 27 | readDiscreteInputs KEYWORD2 28 | readHoldingRegisters KEYWORD2 29 | readInputRegisters KEYWORD2 30 | writeSingleCoil KEYWORD2 31 | writeSingleRegister KEYWORD2 32 | writeMultipleCoils KEYWORD2 33 | writeMultipleRegisters KEYWORD2 34 | maskWriteRegister KEYWORD2 35 | readWriteMultipleRegisters KEYWORD2 36 | 37 | ####################################### 38 | # Constants (LITERAL1) 39 | ####################################### 40 | 41 | ku8MBIllegalFunction LITERAL1 42 | ku8MBIllegalDataAddress LITERAL1 43 | ku8MBIllegalDataValue LITERAL1 44 | ku8MBSlaveDeviceFailure LITERAL1 45 | 46 | ku8MBSuccess LITERAL1 47 | ku8MBInvalidSlaveID LITERAL1 48 | ku8MBInvalidFunction LITERAL1 49 | ku8MBResponseTimedOut LITERAL1 50 | ku8MBInvalidCRC LITERAL1 51 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ModbusMaster 2 | version=2.0.1 3 | author=Doc Walker 4 | maintainer=Doc Walker <4-20ma@wvfans.net> 5 | sentence=Enlighten your Arduino to be a Modbus master. 6 | paragraph=Enables communication with Modbus slaves over RS232/485 (via RTU protocol). Requires an RS232/485 transceiver. 7 | category=Communication 8 | url=https://github.com/4-20ma/ModbusMaster 9 | architectures=* 10 | includes=ModbusMaster.h 11 | -------------------------------------------------------------------------------- /src/ModbusMaster.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | Arduino library for communicating with Modbus slaves over RS232/485 (via RTU protocol). 4 | */ 5 | /* 6 | 7 | ModbusMaster.cpp - Arduino library for communicating with Modbus slaves 8 | over RS232/485 (via RTU protocol). 9 | 10 | Library:: ModbusMaster 11 | 12 | Copyright:: 2009-2016 Doc Walker 13 | 14 | Licensed under the Apache License, Version 2.0 (the "License"); 15 | you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 | 26 | */ 27 | 28 | 29 | /* _____PROJECT INCLUDES_____________________________________________________ */ 30 | #include "ModbusMaster.h" 31 | 32 | 33 | /* _____GLOBAL VARIABLES_____________________________________________________ */ 34 | 35 | 36 | /* _____PUBLIC FUNCTIONS_____________________________________________________ */ 37 | /** 38 | Constructor. 39 | 40 | Creates class object; initialize it using ModbusMaster::begin(). 41 | 42 | @ingroup setup 43 | */ 44 | ModbusMaster::ModbusMaster(void) 45 | { 46 | _idle = 0; 47 | _preTransmission = 0; 48 | _postTransmission = 0; 49 | } 50 | 51 | /** 52 | Initialize class object. 53 | 54 | Assigns the Modbus slave ID and serial port. 55 | Call once class has been instantiated, typically within setup(). 56 | 57 | @param slave Modbus slave ID (1..255) 58 | @param &serial reference to serial port object (Serial, Serial1, ... Serial3) 59 | @ingroup setup 60 | */ 61 | void ModbusMaster::begin(uint8_t slave, Stream &serial) 62 | { 63 | // txBuffer = (uint16_t*) calloc(ku8MaxBufferSize, sizeof(uint16_t)); 64 | _u8MBSlave = slave; 65 | _serial = &serial; 66 | _u8TransmitBufferIndex = 0; 67 | u16TransmitBufferLength = 0; 68 | 69 | #if __MODBUSMASTER_DEBUG__ 70 | pinMode(__MODBUSMASTER_DEBUG_PIN_A__, OUTPUT); 71 | pinMode(__MODBUSMASTER_DEBUG_PIN_B__, OUTPUT); 72 | #endif 73 | } 74 | 75 | 76 | void ModbusMaster::beginTransmission(uint16_t u16Address) 77 | { 78 | _u16WriteAddress = u16Address; 79 | _u8TransmitBufferIndex = 0; 80 | u16TransmitBufferLength = 0; 81 | } 82 | 83 | // eliminate this function in favor of using existing MB request functions 84 | uint8_t ModbusMaster::requestFrom(uint16_t address, uint16_t quantity) 85 | { 86 | uint8_t read; 87 | // clamp to buffer length 88 | if (quantity > ku8MaxBufferSize) 89 | { 90 | quantity = ku8MaxBufferSize; 91 | } 92 | // set rx buffer iterator vars 93 | _u8ResponseBufferIndex = 0; 94 | _u8ResponseBufferLength = read; 95 | 96 | return read; 97 | } 98 | 99 | 100 | void ModbusMaster::sendBit(bool data) 101 | { 102 | uint8_t txBitIndex = u16TransmitBufferLength % 16; 103 | if ((u16TransmitBufferLength >> 4) < ku8MaxBufferSize) 104 | { 105 | if (0 == txBitIndex) 106 | { 107 | _u16TransmitBuffer[_u8TransmitBufferIndex] = 0; 108 | } 109 | bitWrite(_u16TransmitBuffer[_u8TransmitBufferIndex], txBitIndex, data); 110 | u16TransmitBufferLength++; 111 | _u8TransmitBufferIndex = u16TransmitBufferLength >> 4; 112 | } 113 | } 114 | 115 | 116 | void ModbusMaster::send(uint16_t data) 117 | { 118 | if (_u8TransmitBufferIndex < ku8MaxBufferSize) 119 | { 120 | _u16TransmitBuffer[_u8TransmitBufferIndex++] = data; 121 | u16TransmitBufferLength = _u8TransmitBufferIndex << 4; 122 | } 123 | } 124 | 125 | 126 | void ModbusMaster::send(uint32_t data) 127 | { 128 | send(lowWord(data)); 129 | send(highWord(data)); 130 | } 131 | 132 | 133 | void ModbusMaster::send(uint8_t data) 134 | { 135 | send(word(data)); 136 | } 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | uint8_t ModbusMaster::available(void) 147 | { 148 | return _u8ResponseBufferLength - _u8ResponseBufferIndex; 149 | } 150 | 151 | 152 | uint16_t ModbusMaster::receive(void) 153 | { 154 | if (_u8ResponseBufferIndex < _u8ResponseBufferLength) 155 | { 156 | return _u16ResponseBuffer[_u8ResponseBufferIndex++]; 157 | } 158 | else 159 | { 160 | return 0xFFFF; 161 | } 162 | } 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | /** 172 | Set idle time callback function (cooperative multitasking). 173 | 174 | This function gets called in the idle time between transmission of data 175 | and response from slave. Do not call functions that read from the serial 176 | buffer that is used by ModbusMaster. Use of i2c/TWI, 1-Wire, other 177 | serial ports, etc. is permitted within callback function. 178 | 179 | @see ModbusMaster::ModbusMasterTransaction() 180 | */ 181 | void ModbusMaster::idle(void (*idle)()) 182 | { 183 | _idle = idle; 184 | } 185 | 186 | /** 187 | Set pre-transmission callback function. 188 | 189 | This function gets called just before a Modbus message is sent over serial. 190 | Typical usage of this callback is to enable an RS485 transceiver's 191 | Driver Enable pin, and optionally disable its Receiver Enable pin. 192 | 193 | @see ModbusMaster::ModbusMasterTransaction() 194 | @see ModbusMaster::postTransmission() 195 | */ 196 | void ModbusMaster::preTransmission(void (*preTransmission)()) 197 | { 198 | _preTransmission = preTransmission; 199 | } 200 | 201 | /** 202 | Set post-transmission callback function. 203 | 204 | This function gets called after a Modbus message has finished sending 205 | (i.e. after all data has been physically transmitted onto the serial 206 | bus). 207 | 208 | Typical usage of this callback is to enable an RS485 transceiver's 209 | Receiver Enable pin, and disable its Driver Enable pin. 210 | 211 | @see ModbusMaster::ModbusMasterTransaction() 212 | @see ModbusMaster::preTransmission() 213 | */ 214 | void ModbusMaster::postTransmission(void (*postTransmission)()) 215 | { 216 | _postTransmission = postTransmission; 217 | } 218 | 219 | 220 | /** 221 | Retrieve data from response buffer. 222 | 223 | @see ModbusMaster::clearResponseBuffer() 224 | @param u8Index index of response buffer array (0x00..0x3F) 225 | @return value in position u8Index of response buffer (0x0000..0xFFFF) 226 | @ingroup buffer 227 | */ 228 | uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index) 229 | { 230 | if (u8Index < ku8MaxBufferSize) 231 | { 232 | return _u16ResponseBuffer[u8Index]; 233 | } 234 | else 235 | { 236 | return 0xFFFF; 237 | } 238 | } 239 | 240 | 241 | /** 242 | Clear Modbus response buffer. 243 | 244 | @see ModbusMaster::getResponseBuffer(uint8_t u8Index) 245 | @ingroup buffer 246 | */ 247 | void ModbusMaster::clearResponseBuffer() 248 | { 249 | uint8_t i; 250 | 251 | for (i = 0; i < ku8MaxBufferSize; i++) 252 | { 253 | _u16ResponseBuffer[i] = 0; 254 | } 255 | } 256 | 257 | 258 | /** 259 | Place data in transmit buffer. 260 | 261 | @see ModbusMaster::clearTransmitBuffer() 262 | @param u8Index index of transmit buffer array (0x00..0x3F) 263 | @param u16Value value to place in position u8Index of transmit buffer (0x0000..0xFFFF) 264 | @return 0 on success; exception number on failure 265 | @ingroup buffer 266 | */ 267 | uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value) 268 | { 269 | if (u8Index < ku8MaxBufferSize) 270 | { 271 | _u16TransmitBuffer[u8Index] = u16Value; 272 | return ku8MBSuccess; 273 | } 274 | else 275 | { 276 | return ku8MBIllegalDataAddress; 277 | } 278 | } 279 | 280 | 281 | /** 282 | Clear Modbus transmit buffer. 283 | 284 | @see ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value) 285 | @ingroup buffer 286 | */ 287 | void ModbusMaster::clearTransmitBuffer() 288 | { 289 | uint8_t i; 290 | 291 | for (i = 0; i < ku8MaxBufferSize; i++) 292 | { 293 | _u16TransmitBuffer[i] = 0; 294 | } 295 | } 296 | 297 | 298 | /** 299 | Modbus function 0x01 Read Coils. 300 | 301 | This function code is used to read from 1 to 2000 contiguous status of 302 | coils in a remote device. The request specifies the starting address, 303 | i.e. the address of the first coil specified, and the number of coils. 304 | Coils are addressed starting at zero. 305 | 306 | The coils in the response buffer are packed as one coil per bit of the 307 | data field. Status is indicated as 1=ON and 0=OFF. The LSB of the first 308 | data word contains the output addressed in the query. The other coils 309 | follow toward the high order end of this word and from low order to high 310 | order in subsequent words. 311 | 312 | If the returned quantity is not a multiple of sixteen, the remaining 313 | bits in the final data word will be padded with zeros (toward the high 314 | order end of the word). 315 | 316 | @param u16ReadAddress address of first coil (0x0000..0xFFFF) 317 | @param u16BitQty quantity of coils to read (1..2000, enforced by remote device) 318 | @return 0 on success; exception number on failure 319 | @ingroup discrete 320 | */ 321 | uint8_t ModbusMaster::readCoils(uint16_t u16ReadAddress, uint16_t u16BitQty) 322 | { 323 | _u16ReadAddress = u16ReadAddress; 324 | _u16ReadQty = u16BitQty; 325 | return ModbusMasterTransaction(ku8MBReadCoils); 326 | } 327 | 328 | 329 | /** 330 | Modbus function 0x02 Read Discrete Inputs. 331 | 332 | This function code is used to read from 1 to 2000 contiguous status of 333 | discrete inputs in a remote device. The request specifies the starting 334 | address, i.e. the address of the first input specified, and the number 335 | of inputs. Discrete inputs are addressed starting at zero. 336 | 337 | The discrete inputs in the response buffer are packed as one input per 338 | bit of the data field. Status is indicated as 1=ON; 0=OFF. The LSB of 339 | the first data word contains the input addressed in the query. The other 340 | inputs follow toward the high order end of this word, and from low order 341 | to high order in subsequent words. 342 | 343 | If the returned quantity is not a multiple of sixteen, the remaining 344 | bits in the final data word will be padded with zeros (toward the high 345 | order end of the word). 346 | 347 | @param u16ReadAddress address of first discrete input (0x0000..0xFFFF) 348 | @param u16BitQty quantity of discrete inputs to read (1..2000, enforced by remote device) 349 | @return 0 on success; exception number on failure 350 | @ingroup discrete 351 | */ 352 | uint8_t ModbusMaster::readDiscreteInputs(uint16_t u16ReadAddress, 353 | uint16_t u16BitQty) 354 | { 355 | _u16ReadAddress = u16ReadAddress; 356 | _u16ReadQty = u16BitQty; 357 | return ModbusMasterTransaction(ku8MBReadDiscreteInputs); 358 | } 359 | 360 | 361 | /** 362 | Modbus function 0x03 Read Holding Registers. 363 | 364 | This function code is used to read the contents of a contiguous block of 365 | holding registers in a remote device. The request specifies the starting 366 | register address and the number of registers. Registers are addressed 367 | starting at zero. 368 | 369 | The register data in the response buffer is packed as one word per 370 | register. 371 | 372 | @param u16ReadAddress address of the first holding register (0x0000..0xFFFF) 373 | @param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device) 374 | @return 0 on success; exception number on failure 375 | @ingroup register 376 | */ 377 | uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress, 378 | uint16_t u16ReadQty) 379 | { 380 | _u16ReadAddress = u16ReadAddress; 381 | _u16ReadQty = u16ReadQty; 382 | return ModbusMasterTransaction(ku8MBReadHoldingRegisters); 383 | } 384 | 385 | 386 | /** 387 | Modbus function 0x04 Read Input Registers. 388 | 389 | This function code is used to read from 1 to 125 contiguous input 390 | registers in a remote device. The request specifies the starting 391 | register address and the number of registers. Registers are addressed 392 | starting at zero. 393 | 394 | The register data in the response buffer is packed as one word per 395 | register. 396 | 397 | @param u16ReadAddress address of the first input register (0x0000..0xFFFF) 398 | @param u16ReadQty quantity of input registers to read (1..125, enforced by remote device) 399 | @return 0 on success; exception number on failure 400 | @ingroup register 401 | */ 402 | uint8_t ModbusMaster::readInputRegisters(uint16_t u16ReadAddress, 403 | uint8_t u16ReadQty) 404 | { 405 | _u16ReadAddress = u16ReadAddress; 406 | _u16ReadQty = u16ReadQty; 407 | return ModbusMasterTransaction(ku8MBReadInputRegisters); 408 | } 409 | 410 | 411 | /** 412 | Modbus function 0x05 Write Single Coil. 413 | 414 | This function code is used to write a single output to either ON or OFF 415 | in a remote device. The requested ON/OFF state is specified by a 416 | constant in the state field. A non-zero value requests the output to be 417 | ON and a value of 0 requests it to be OFF. The request specifies the 418 | address of the coil to be forced. Coils are addressed starting at zero. 419 | 420 | @param u16WriteAddress address of the coil (0x0000..0xFFFF) 421 | @param u8State 0=OFF, non-zero=ON (0x00..0xFF) 422 | @return 0 on success; exception number on failure 423 | @ingroup discrete 424 | */ 425 | uint8_t ModbusMaster::writeSingleCoil(uint16_t u16WriteAddress, uint8_t u8State) 426 | { 427 | _u16WriteAddress = u16WriteAddress; 428 | _u16WriteQty = (u8State ? 0xFF00 : 0x0000); 429 | return ModbusMasterTransaction(ku8MBWriteSingleCoil); 430 | } 431 | 432 | 433 | /** 434 | Modbus function 0x06 Write Single Register. 435 | 436 | This function code is used to write a single holding register in a 437 | remote device. The request specifies the address of the register to be 438 | written. Registers are addressed starting at zero. 439 | 440 | @param u16WriteAddress address of the holding register (0x0000..0xFFFF) 441 | @param u16WriteValue value to be written to holding register (0x0000..0xFFFF) 442 | @return 0 on success; exception number on failure 443 | @ingroup register 444 | */ 445 | uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, 446 | uint16_t u16WriteValue) 447 | { 448 | _u16WriteAddress = u16WriteAddress; 449 | _u16WriteQty = 0; 450 | _u16TransmitBuffer[0] = u16WriteValue; 451 | return ModbusMasterTransaction(ku8MBWriteSingleRegister); 452 | } 453 | 454 | 455 | /** 456 | Modbus function 0x0F Write Multiple Coils. 457 | 458 | This function code is used to force each coil in a sequence of coils to 459 | either ON or OFF in a remote device. The request specifies the coil 460 | references to be forced. Coils are addressed starting at zero. 461 | 462 | The requested ON/OFF states are specified by contents of the transmit 463 | buffer. A logical '1' in a bit position of the buffer requests the 464 | corresponding output to be ON. A logical '0' requests it to be OFF. 465 | 466 | @param u16WriteAddress address of the first coil (0x0000..0xFFFF) 467 | @param u16BitQty quantity of coils to write (1..2000, enforced by remote device) 468 | @return 0 on success; exception number on failure 469 | @ingroup discrete 470 | */ 471 | uint8_t ModbusMaster::writeMultipleCoils(uint16_t u16WriteAddress, 472 | uint16_t u16BitQty) 473 | { 474 | _u16WriteAddress = u16WriteAddress; 475 | _u16WriteQty = u16BitQty; 476 | return ModbusMasterTransaction(ku8MBWriteMultipleCoils); 477 | } 478 | uint8_t ModbusMaster::writeMultipleCoils() 479 | { 480 | _u16WriteQty = u16TransmitBufferLength; 481 | return ModbusMasterTransaction(ku8MBWriteMultipleCoils); 482 | } 483 | 484 | 485 | /** 486 | Modbus function 0x10 Write Multiple Registers. 487 | 488 | This function code is used to write a block of contiguous registers (1 489 | to 123 registers) in a remote device. 490 | 491 | The requested written values are specified in the transmit buffer. Data 492 | is packed as one word per register. 493 | 494 | @param u16WriteAddress address of the holding register (0x0000..0xFFFF) 495 | @param u16WriteQty quantity of holding registers to write (1..123, enforced by remote device) 496 | @return 0 on success; exception number on failure 497 | @ingroup register 498 | */ 499 | uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress, 500 | uint16_t u16WriteQty) 501 | { 502 | _u16WriteAddress = u16WriteAddress; 503 | _u16WriteQty = u16WriteQty; 504 | return ModbusMasterTransaction(ku8MBWriteMultipleRegisters); 505 | } 506 | 507 | // new version based on Wire.h 508 | uint8_t ModbusMaster::writeMultipleRegisters() 509 | { 510 | _u16WriteQty = _u8TransmitBufferIndex; 511 | return ModbusMasterTransaction(ku8MBWriteMultipleRegisters); 512 | } 513 | 514 | 515 | /** 516 | Modbus function 0x16 Mask Write Register. 517 | 518 | This function code is used to modify the contents of a specified holding 519 | register using a combination of an AND mask, an OR mask, and the 520 | register's current contents. The function can be used to set or clear 521 | individual bits in the register. 522 | 523 | The request specifies the holding register to be written, the data to be 524 | used as the AND mask, and the data to be used as the OR mask. Registers 525 | are addressed starting at zero. 526 | 527 | The function's algorithm is: 528 | 529 | Result = (Current Contents && And_Mask) || (Or_Mask && (~And_Mask)) 530 | 531 | @param u16WriteAddress address of the holding register (0x0000..0xFFFF) 532 | @param u16AndMask AND mask (0x0000..0xFFFF) 533 | @param u16OrMask OR mask (0x0000..0xFFFF) 534 | @return 0 on success; exception number on failure 535 | @ingroup register 536 | */ 537 | uint8_t ModbusMaster::maskWriteRegister(uint16_t u16WriteAddress, 538 | uint16_t u16AndMask, uint16_t u16OrMask) 539 | { 540 | _u16WriteAddress = u16WriteAddress; 541 | _u16TransmitBuffer[0] = u16AndMask; 542 | _u16TransmitBuffer[1] = u16OrMask; 543 | return ModbusMasterTransaction(ku8MBMaskWriteRegister); 544 | } 545 | 546 | 547 | /** 548 | Modbus function 0x17 Read Write Multiple Registers. 549 | 550 | This function code performs a combination of one read operation and one 551 | write operation in a single MODBUS transaction. The write operation is 552 | performed before the read. Holding registers are addressed starting at 553 | zero. 554 | 555 | The request specifies the starting address and number of holding 556 | registers to be read as well as the starting address, and the number of 557 | holding registers. The data to be written is specified in the transmit 558 | buffer. 559 | 560 | @param u16ReadAddress address of the first holding register (0x0000..0xFFFF) 561 | @param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device) 562 | @param u16WriteAddress address of the first holding register (0x0000..0xFFFF) 563 | @param u16WriteQty quantity of holding registers to write (1..121, enforced by remote device) 564 | @return 0 on success; exception number on failure 565 | @ingroup register 566 | */ 567 | uint8_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, 568 | uint16_t u16ReadQty, uint16_t u16WriteAddress, uint16_t u16WriteQty) 569 | { 570 | _u16ReadAddress = u16ReadAddress; 571 | _u16ReadQty = u16ReadQty; 572 | _u16WriteAddress = u16WriteAddress; 573 | _u16WriteQty = u16WriteQty; 574 | return ModbusMasterTransaction(ku8MBReadWriteMultipleRegisters); 575 | } 576 | uint8_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, 577 | uint16_t u16ReadQty) 578 | { 579 | _u16ReadAddress = u16ReadAddress; 580 | _u16ReadQty = u16ReadQty; 581 | _u16WriteQty = _u8TransmitBufferIndex; 582 | return ModbusMasterTransaction(ku8MBReadWriteMultipleRegisters); 583 | } 584 | 585 | 586 | /* _____PRIVATE FUNCTIONS____________________________________________________ */ 587 | /** 588 | Modbus transaction engine. 589 | Sequence: 590 | - assemble Modbus Request Application Data Unit (ADU), 591 | based on particular function called 592 | - transmit request over selected serial port 593 | - wait for/retrieve response 594 | - evaluate/disassemble response 595 | - return status (success/exception) 596 | 597 | @param u8MBFunction Modbus function (0x01..0xFF) 598 | @return 0 on success; exception number on failure 599 | */ 600 | uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) 601 | { 602 | uint8_t u8ModbusADU[256]; 603 | uint8_t u8ModbusADUSize = 0; 604 | uint8_t i, u8Qty; 605 | uint16_t u16CRC; 606 | uint32_t u32StartTime; 607 | uint8_t u8BytesLeft = 8; 608 | uint8_t u8MBStatus = ku8MBSuccess; 609 | 610 | // assemble Modbus Request Application Data Unit 611 | u8ModbusADU[u8ModbusADUSize++] = _u8MBSlave; 612 | u8ModbusADU[u8ModbusADUSize++] = u8MBFunction; 613 | 614 | switch(u8MBFunction) 615 | { 616 | case ku8MBReadCoils: 617 | case ku8MBReadDiscreteInputs: 618 | case ku8MBReadInputRegisters: 619 | case ku8MBReadHoldingRegisters: 620 | case ku8MBReadWriteMultipleRegisters: 621 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadAddress); 622 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadAddress); 623 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadQty); 624 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadQty); 625 | break; 626 | } 627 | 628 | switch(u8MBFunction) 629 | { 630 | case ku8MBWriteSingleCoil: 631 | case ku8MBMaskWriteRegister: 632 | case ku8MBWriteMultipleCoils: 633 | case ku8MBWriteSingleRegister: 634 | case ku8MBWriteMultipleRegisters: 635 | case ku8MBReadWriteMultipleRegisters: 636 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteAddress); 637 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteAddress); 638 | break; 639 | } 640 | 641 | switch(u8MBFunction) 642 | { 643 | case ku8MBWriteSingleCoil: 644 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty); 645 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty); 646 | break; 647 | 648 | case ku8MBWriteSingleRegister: 649 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[0]); 650 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[0]); 651 | break; 652 | 653 | case ku8MBWriteMultipleCoils: 654 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty); 655 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty); 656 | u8Qty = (_u16WriteQty % 8) ? ((_u16WriteQty >> 3) + 1) : (_u16WriteQty >> 3); 657 | u8ModbusADU[u8ModbusADUSize++] = u8Qty; 658 | for (i = 0; i < u8Qty; i++) 659 | { 660 | switch(i % 2) 661 | { 662 | case 0: // i is even 663 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i >> 1]); 664 | break; 665 | 666 | case 1: // i is odd 667 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i >> 1]); 668 | break; 669 | } 670 | } 671 | break; 672 | 673 | case ku8MBWriteMultipleRegisters: 674 | case ku8MBReadWriteMultipleRegisters: 675 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty); 676 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty); 677 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty << 1); 678 | 679 | for (i = 0; i < lowByte(_u16WriteQty); i++) 680 | { 681 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i]); 682 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i]); 683 | } 684 | break; 685 | 686 | case ku8MBMaskWriteRegister: 687 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[0]); 688 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[0]); 689 | u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[1]); 690 | u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[1]); 691 | break; 692 | } 693 | 694 | // append CRC 695 | u16CRC = 0xFFFF; 696 | for (i = 0; i < u8ModbusADUSize; i++) 697 | { 698 | u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); 699 | } 700 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC); 701 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); 702 | u8ModbusADU[u8ModbusADUSize] = 0; 703 | 704 | // flush receive buffer before transmitting request 705 | while (_serial->read() != -1); 706 | 707 | // transmit request 708 | if (_preTransmission) 709 | { 710 | _preTransmission(); 711 | } 712 | for (i = 0; i < u8ModbusADUSize; i++) 713 | { 714 | _serial->write(u8ModbusADU[i]); 715 | } 716 | 717 | u8ModbusADUSize = 0; 718 | _serial->flush(); // flush transmit buffer 719 | if (_postTransmission) 720 | { 721 | _postTransmission(); 722 | } 723 | 724 | // loop until we run out of time or bytes, or an error occurs 725 | u32StartTime = millis(); 726 | while (u8BytesLeft && !u8MBStatus) 727 | { 728 | if (_serial->available()) 729 | { 730 | #if __MODBUSMASTER_DEBUG__ 731 | digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, true); 732 | #endif 733 | u8ModbusADU[u8ModbusADUSize++] = _serial->read(); 734 | u8BytesLeft--; 735 | #if __MODBUSMASTER_DEBUG__ 736 | digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false); 737 | #endif 738 | } 739 | else 740 | { 741 | #if __MODBUSMASTER_DEBUG__ 742 | digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, true); 743 | #endif 744 | if (_idle) 745 | { 746 | _idle(); 747 | } 748 | #if __MODBUSMASTER_DEBUG__ 749 | digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, false); 750 | #endif 751 | } 752 | 753 | // evaluate slave ID, function code once enough bytes have been read 754 | if (u8ModbusADUSize == 5) 755 | { 756 | // verify response is for correct Modbus slave 757 | if (u8ModbusADU[0] != _u8MBSlave) 758 | { 759 | u8MBStatus = ku8MBInvalidSlaveID; 760 | break; 761 | } 762 | 763 | // verify response is for correct Modbus function code (mask exception bit 7) 764 | if ((u8ModbusADU[1] & 0x7F) != u8MBFunction) 765 | { 766 | u8MBStatus = ku8MBInvalidFunction; 767 | break; 768 | } 769 | 770 | // check whether Modbus exception occurred; return Modbus Exception Code 771 | if (bitRead(u8ModbusADU[1], 7)) 772 | { 773 | u8MBStatus = u8ModbusADU[2]; 774 | break; 775 | } 776 | 777 | // evaluate returned Modbus function code 778 | switch(u8ModbusADU[1]) 779 | { 780 | case ku8MBReadCoils: 781 | case ku8MBReadDiscreteInputs: 782 | case ku8MBReadInputRegisters: 783 | case ku8MBReadHoldingRegisters: 784 | case ku8MBReadWriteMultipleRegisters: 785 | u8BytesLeft = u8ModbusADU[2]; 786 | break; 787 | 788 | case ku8MBWriteSingleCoil: 789 | case ku8MBWriteMultipleCoils: 790 | case ku8MBWriteSingleRegister: 791 | case ku8MBWriteMultipleRegisters: 792 | u8BytesLeft = 3; 793 | break; 794 | 795 | case ku8MBMaskWriteRegister: 796 | u8BytesLeft = 5; 797 | break; 798 | } 799 | } 800 | if ((millis() - u32StartTime) > ku16MBResponseTimeout) 801 | { 802 | u8MBStatus = ku8MBResponseTimedOut; 803 | } 804 | } 805 | 806 | // verify response is large enough to inspect further 807 | if (!u8MBStatus && u8ModbusADUSize >= 5) 808 | { 809 | // calculate CRC 810 | u16CRC = 0xFFFF; 811 | for (i = 0; i < (u8ModbusADUSize - 2); i++) 812 | { 813 | u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); 814 | } 815 | 816 | // verify CRC 817 | if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] || 818 | highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])) 819 | { 820 | u8MBStatus = ku8MBInvalidCRC; 821 | } 822 | } 823 | 824 | // disassemble ADU into words 825 | if (!u8MBStatus) 826 | { 827 | // evaluate returned Modbus function code 828 | switch(u8ModbusADU[1]) 829 | { 830 | case ku8MBReadCoils: 831 | case ku8MBReadDiscreteInputs: 832 | // load bytes into word; response bytes are ordered L, H, L, H, ... 833 | for (i = 0; i < (u8ModbusADU[2] >> 1); i++) 834 | { 835 | if (i < ku8MaxBufferSize) 836 | { 837 | _u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 4], u8ModbusADU[2 * i + 3]); 838 | } 839 | 840 | _u8ResponseBufferLength = i; 841 | } 842 | 843 | // in the event of an odd number of bytes, load last byte into zero-padded word 844 | if (u8ModbusADU[2] % 2) 845 | { 846 | if (i < ku8MaxBufferSize) 847 | { 848 | _u16ResponseBuffer[i] = word(0, u8ModbusADU[2 * i + 3]); 849 | } 850 | 851 | _u8ResponseBufferLength = i + 1; 852 | } 853 | break; 854 | 855 | case ku8MBReadInputRegisters: 856 | case ku8MBReadHoldingRegisters: 857 | case ku8MBReadWriteMultipleRegisters: 858 | // load bytes into word; response bytes are ordered H, L, H, L, ... 859 | for (i = 0; i < (u8ModbusADU[2] >> 1); i++) 860 | { 861 | if (i < ku8MaxBufferSize) 862 | { 863 | _u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]); 864 | } 865 | 866 | _u8ResponseBufferLength = i; 867 | } 868 | break; 869 | } 870 | } 871 | 872 | _u8TransmitBufferIndex = 0; 873 | u16TransmitBufferLength = 0; 874 | _u8ResponseBufferIndex = 0; 875 | return u8MBStatus; 876 | } 877 | -------------------------------------------------------------------------------- /src/ModbusMaster.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | Arduino library for communicating with Modbus slaves over RS232/485 (via RTU protocol). 4 | 5 | @defgroup setup ModbusMaster Object Instantiation/Initialization 6 | @defgroup buffer ModbusMaster Buffer Management 7 | @defgroup discrete Modbus Function Codes for Discrete Coils/Inputs 8 | @defgroup register Modbus Function Codes for Holding/Input Registers 9 | @defgroup constant Modbus Function Codes, Exception Codes 10 | */ 11 | /* 12 | 13 | ModbusMaster.h - Arduino library for communicating with Modbus slaves 14 | over RS232/485 (via RTU protocol). 15 | 16 | Library:: ModbusMaster 17 | 18 | Copyright:: 2009-2016 Doc Walker 19 | 20 | Licensed under the Apache License, Version 2.0 (the "License"); 21 | you may not use this file except in compliance with the License. 22 | You may obtain a copy of the License at 23 | 24 | http://www.apache.org/licenses/LICENSE-2.0 25 | 26 | Unless required by applicable law or agreed to in writing, software 27 | distributed under the License is distributed on an "AS IS" BASIS, 28 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | See the License for the specific language governing permissions and 30 | limitations under the License. 31 | 32 | */ 33 | 34 | 35 | #ifndef ModbusMaster_h 36 | #define ModbusMaster_h 37 | 38 | 39 | /** 40 | @def __MODBUSMASTER_DEBUG__ (0) 41 | Set to 1 to enable debugging features within class: 42 | - PIN A cycles for each byte read in the Modbus response 43 | - PIN B cycles for each millisecond timeout during the Modbus response 44 | */ 45 | #define __MODBUSMASTER_DEBUG__ (0) 46 | #define __MODBUSMASTER_DEBUG_PIN_A__ 4 47 | #define __MODBUSMASTER_DEBUG_PIN_B__ 5 48 | 49 | /* _____STANDARD INCLUDES____________________________________________________ */ 50 | // include types & constants of Wiring core API 51 | #include "Arduino.h" 52 | 53 | /* _____UTILITY MACROS_______________________________________________________ */ 54 | 55 | 56 | /* _____PROJECT INCLUDES_____________________________________________________ */ 57 | // functions to calculate Modbus Application Data Unit CRC 58 | #include "util/crc16.h" 59 | 60 | // functions to manipulate words 61 | #include "util/word.h" 62 | 63 | 64 | /* _____CLASS DEFINITIONS____________________________________________________ */ 65 | /** 66 | Arduino class library for communicating with Modbus slaves over 67 | RS232/485 (via RTU protocol). 68 | */ 69 | class ModbusMaster 70 | { 71 | public: 72 | ModbusMaster(); 73 | 74 | void begin(uint8_t, Stream &serial); 75 | void idle(void (*)()); 76 | void preTransmission(void (*)()); 77 | void postTransmission(void (*)()); 78 | 79 | // Modbus exception codes 80 | /** 81 | Modbus protocol illegal function exception. 82 | 83 | The function code received in the query is not an allowable action for 84 | the server (or slave). This may be because the function code is only 85 | applicable to newer devices, and was not implemented in the unit 86 | selected. It could also indicate that the server (or slave) is in the 87 | wrong state to process a request of this type, for example because it is 88 | unconfigured and is being asked to return register values. 89 | 90 | @ingroup constant 91 | */ 92 | static const uint8_t ku8MBIllegalFunction = 0x01; 93 | 94 | /** 95 | Modbus protocol illegal data address exception. 96 | 97 | The data address received in the query is not an allowable address for 98 | the server (or slave). More specifically, the combination of reference 99 | number and transfer length is invalid. For a controller with 100 100 | registers, the ADU addresses the first register as 0, and the last one 101 | as 99. If a request is submitted with a starting register address of 96 102 | and a quantity of registers of 4, then this request will successfully 103 | operate (address-wise at least) on registers 96, 97, 98, 99. If a 104 | request is submitted with a starting register address of 96 and a 105 | quantity of registers of 5, then this request will fail with Exception 106 | Code 0x02 "Illegal Data Address" since it attempts to operate on 107 | registers 96, 97, 98, 99 and 100, and there is no register with address 108 | 100. 109 | 110 | @ingroup constant 111 | */ 112 | static const uint8_t ku8MBIllegalDataAddress = 0x02; 113 | 114 | /** 115 | Modbus protocol illegal data value exception. 116 | 117 | A value contained in the query data field is not an allowable value for 118 | server (or slave). This indicates a fault in the structure of the 119 | remainder of a complex request, such as that the implied length is 120 | incorrect. It specifically does NOT mean that a data item submitted for 121 | storage in a register has a value outside the expectation of the 122 | application program, since the MODBUS protocol is unaware of the 123 | significance of any particular value of any particular register. 124 | 125 | @ingroup constant 126 | */ 127 | static const uint8_t ku8MBIllegalDataValue = 0x03; 128 | 129 | /** 130 | Modbus protocol slave device failure exception. 131 | 132 | An unrecoverable error occurred while the server (or slave) was 133 | attempting to perform the requested action. 134 | 135 | @ingroup constant 136 | */ 137 | static const uint8_t ku8MBSlaveDeviceFailure = 0x04; 138 | 139 | // Class-defined success/exception codes 140 | /** 141 | ModbusMaster success. 142 | 143 | Modbus transaction was successful; the following checks were valid: 144 | - slave ID 145 | - function code 146 | - response code 147 | - data 148 | - CRC 149 | 150 | @ingroup constant 151 | */ 152 | static const uint8_t ku8MBSuccess = 0x00; 153 | 154 | /** 155 | ModbusMaster invalid response slave ID exception. 156 | 157 | The slave ID in the response does not match that of the request. 158 | 159 | @ingroup constant 160 | */ 161 | static const uint8_t ku8MBInvalidSlaveID = 0xE0; 162 | 163 | /** 164 | ModbusMaster invalid response function exception. 165 | 166 | The function code in the response does not match that of the request. 167 | 168 | @ingroup constant 169 | */ 170 | static const uint8_t ku8MBInvalidFunction = 0xE1; 171 | 172 | /** 173 | ModbusMaster response timed out exception. 174 | 175 | The entire response was not received within the timeout period, 176 | ModbusMaster::ku8MBResponseTimeout. 177 | 178 | @ingroup constant 179 | */ 180 | static const uint8_t ku8MBResponseTimedOut = 0xE2; 181 | 182 | /** 183 | ModbusMaster invalid response CRC exception. 184 | 185 | The CRC in the response does not match the one calculated. 186 | 187 | @ingroup constant 188 | */ 189 | static const uint8_t ku8MBInvalidCRC = 0xE3; 190 | 191 | uint16_t getResponseBuffer(uint8_t); 192 | void clearResponseBuffer(); 193 | uint8_t setTransmitBuffer(uint8_t, uint16_t); 194 | void clearTransmitBuffer(); 195 | 196 | void beginTransmission(uint16_t); 197 | uint8_t requestFrom(uint16_t, uint16_t); 198 | void sendBit(bool); 199 | void send(uint8_t); 200 | void send(uint16_t); 201 | void send(uint32_t); 202 | uint8_t available(void); 203 | uint16_t receive(void); 204 | 205 | 206 | uint8_t readCoils(uint16_t, uint16_t); 207 | uint8_t readDiscreteInputs(uint16_t, uint16_t); 208 | uint8_t readHoldingRegisters(uint16_t, uint16_t); 209 | uint8_t readInputRegisters(uint16_t, uint8_t); 210 | uint8_t writeSingleCoil(uint16_t, uint8_t); 211 | uint8_t writeSingleRegister(uint16_t, uint16_t); 212 | uint8_t writeMultipleCoils(uint16_t, uint16_t); 213 | uint8_t writeMultipleCoils(); 214 | uint8_t writeMultipleRegisters(uint16_t, uint16_t); 215 | uint8_t writeMultipleRegisters(); 216 | uint8_t maskWriteRegister(uint16_t, uint16_t, uint16_t); 217 | uint8_t readWriteMultipleRegisters(uint16_t, uint16_t, uint16_t, uint16_t); 218 | uint8_t readWriteMultipleRegisters(uint16_t, uint16_t); 219 | 220 | private: 221 | Stream* _serial; ///< reference to serial port object 222 | uint8_t _u8MBSlave; ///< Modbus slave (1..255) initialized in begin() 223 | static const uint8_t ku8MaxBufferSize = 64; ///< size of response/transmit buffers 224 | uint16_t _u16ReadAddress; ///< slave register from which to read 225 | uint16_t _u16ReadQty; ///< quantity of words to read 226 | uint16_t _u16ResponseBuffer[ku8MaxBufferSize]; ///< buffer to store Modbus slave response; read via GetResponseBuffer() 227 | uint16_t _u16WriteAddress; ///< slave register to which to write 228 | uint16_t _u16WriteQty; ///< quantity of words to write 229 | uint16_t _u16TransmitBuffer[ku8MaxBufferSize]; ///< buffer containing data to transmit to Modbus slave; set via SetTransmitBuffer() 230 | uint16_t* txBuffer; // from Wire.h -- need to clean this up Rx 231 | uint8_t _u8TransmitBufferIndex; 232 | uint16_t u16TransmitBufferLength; 233 | uint16_t* rxBuffer; // from Wire.h -- need to clean this up Rx 234 | uint8_t _u8ResponseBufferIndex; 235 | uint8_t _u8ResponseBufferLength; 236 | 237 | // Modbus function codes for bit access 238 | static const uint8_t ku8MBReadCoils = 0x01; ///< Modbus function 0x01 Read Coils 239 | static const uint8_t ku8MBReadDiscreteInputs = 0x02; ///< Modbus function 0x02 Read Discrete Inputs 240 | static const uint8_t ku8MBWriteSingleCoil = 0x05; ///< Modbus function 0x05 Write Single Coil 241 | static const uint8_t ku8MBWriteMultipleCoils = 0x0F; ///< Modbus function 0x0F Write Multiple Coils 242 | 243 | // Modbus function codes for 16 bit access 244 | static const uint8_t ku8MBReadHoldingRegisters = 0x03; ///< Modbus function 0x03 Read Holding Registers 245 | static const uint8_t ku8MBReadInputRegisters = 0x04; ///< Modbus function 0x04 Read Input Registers 246 | static const uint8_t ku8MBWriteSingleRegister = 0x06; ///< Modbus function 0x06 Write Single Register 247 | static const uint8_t ku8MBWriteMultipleRegisters = 0x10; ///< Modbus function 0x10 Write Multiple Registers 248 | static const uint8_t ku8MBMaskWriteRegister = 0x16; ///< Modbus function 0x16 Mask Write Register 249 | static const uint8_t ku8MBReadWriteMultipleRegisters = 0x17; ///< Modbus function 0x17 Read Write Multiple Registers 250 | 251 | // Modbus timeout [milliseconds] 252 | static const uint16_t ku16MBResponseTimeout = 2000; ///< Modbus timeout [milliseconds] 253 | 254 | // master function that conducts Modbus transactions 255 | uint8_t ModbusMasterTransaction(uint8_t u8MBFunction); 256 | 257 | // idle callback function; gets called during idle time between TX and RX 258 | void (*_idle)(); 259 | // preTransmission callback function; gets called before writing a Modbus message 260 | void (*_preTransmission)(); 261 | // postTransmission callback function; gets called after a Modbus message has been sent 262 | void (*_postTransmission)(); 263 | }; 264 | #endif 265 | 266 | /** 267 | @example examples/Basic/Basic.pde 268 | @example examples/PhoenixContact_nanoLC/PhoenixContact_nanoLC.pde 269 | @example examples/RS485_HalfDuplex/RS485_HalfDuplex.ino 270 | */ 271 | -------------------------------------------------------------------------------- /src/util/crc16.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | CRC Computations 4 | 5 | @defgroup util_crc16 "util/crc16.h": CRC Computations 6 | @code#include "util/crc16.h"@endcode 7 | 8 | This header file provides functions for calculating 9 | cyclic redundancy checks (CRC) using common polynomials. 10 | Modified by Doc Walker to be processor-independent (removed inline 11 | assembler to allow it to compile on SAM3X8E processors). 12 | 13 | @par References: 14 | Jack Crenshaw's "Implementing CRCs" article in the January 1992 issue of @e 15 | Embedded @e Systems @e Programming. This may be difficult to find, but it 16 | explains CRC's in very clear and concise terms. Well worth the effort to 17 | obtain a copy. 18 | 19 | */ 20 | /* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz 21 | Copyright (c) 2005, 2007 Joerg Wunsch 22 | Copyright (c) 2013 Dave Hylands 23 | Copyright (c) 2013 Frederic Nadeau 24 | Copyright (c) 2015 Doc Walker 25 | All rights reserved. 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions are met: 29 | 30 | * Redistributions of source code must retain the above copyright 31 | notice, this list of conditions and the following disclaimer. 32 | 33 | * Redistributions in binary form must reproduce the above copyright 34 | notice, this list of conditions and the following disclaimer in 35 | the documentation and/or other materials provided with the 36 | distribution. 37 | 38 | * Neither the name of the copyright holders nor the names of 39 | contributors may be used to endorse or promote products derived 40 | from this software without specific prior written permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 43 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 44 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 45 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 46 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 47 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 48 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 49 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 50 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 51 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 52 | POSSIBILITY OF SUCH DAMAGE. */ 53 | 54 | 55 | #ifndef _UTIL_CRC16_H_ 56 | #define _UTIL_CRC16_H_ 57 | 58 | 59 | /** @ingroup util_crc16 60 | Processor-independent CRC-16 calculation. 61 | 62 | Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)
63 | Initial value: 0xFFFF 64 | 65 | This CRC is normally used in disk-drive controllers. 66 | 67 | @param uint16_t crc (0x0000..0xFFFF) 68 | @param uint8_t a (0x00..0xFF) 69 | @return calculated CRC (0x0000..0xFFFF) 70 | */ 71 | static uint16_t crc16_update(uint16_t crc, uint8_t a) 72 | { 73 | int i; 74 | 75 | crc ^= a; 76 | for (i = 0; i < 8; ++i) 77 | { 78 | if (crc & 1) 79 | crc = (crc >> 1) ^ 0xA001; 80 | else 81 | crc = (crc >> 1); 82 | } 83 | 84 | return crc; 85 | } 86 | 87 | 88 | #endif /* _UTIL_CRC16_H_ */ 89 | -------------------------------------------------------------------------------- /src/util/word.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | Utility Functions for Manipulating Words 4 | 5 | @defgroup util_word "util/word.h": Utility Functions for Manipulating Words 6 | @code#include "util/word.h"@endcode 7 | 8 | This header file provides utility functions for manipulating words. 9 | 10 | */ 11 | /* 12 | 13 | word.h - Utility Functions for Manipulating Words 14 | 15 | This file is part of ModbusMaster. 16 | 17 | ModbusMaster is free software: you can redistribute it and/or modify 18 | it under the terms of the GNU General Public License as published by 19 | the Free Software Foundation, either version 3 of the License, or 20 | (at your option) any later version. 21 | 22 | ModbusMaster is distributed in the hope that it will be useful, 23 | but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | GNU General Public License for more details. 26 | 27 | You should have received a copy of the GNU General Public License 28 | along with ModbusMaster. If not, see . 29 | 30 | Written by Doc Walker (Rx) 31 | Copyright © 2009-2015 Doc Walker <4-20ma at wvfans dot net> 32 | 33 | */ 34 | 35 | 36 | #ifndef _UTIL_WORD_H_ 37 | #define _UTIL_WORD_H_ 38 | 39 | 40 | /** @ingroup util_word 41 | Return low word of a 32-bit integer. 42 | 43 | @param uint32_t ww (0x00000000..0xFFFFFFFF) 44 | @return low word of input (0x0000..0xFFFF) 45 | */ 46 | static inline uint16_t lowWord(uint32_t ww) 47 | { 48 | return (uint16_t) ((ww) & 0xFFFF); 49 | } 50 | 51 | 52 | /** @ingroup util_word 53 | Return high word of a 32-bit integer. 54 | 55 | @param uint32_t ww (0x00000000..0xFFFFFFFF) 56 | @return high word of input (0x0000..0xFFFF) 57 | */ 58 | static inline uint16_t highWord(uint32_t ww) 59 | { 60 | return (uint16_t) ((ww) >> 16); 61 | } 62 | 63 | 64 | #endif /* _UTIL_WORD_H_ */ 65 | --------------------------------------------------------------------------------