├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── code ├── Main_board-Blue_Pill │ ├── Main_Modbus_HMI_BluePill_v01.c │ └── Main_Modbus_HMI_BluePill_v02.c ├── Main_board-STM32_NUCLEO-F446RE │ ├── Main_Modbus_HMI_F446RE_v01.c │ └── Main_Modbus_HMI_F446RE_v02.c ├── Test_board-Blue_Pill │ └── Test_Modbus_HMI_BluePill_v01.c └── Test_board-STM32_NUCLEO-F446RE │ └── Test_Modbus_HMI_F446RE_v01.c └── library ├── checksum.h └── crc16.c /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | hsienching.chung@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hsien-Ching Chung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 19 | 27 | 28 | 29 | 30 | 53 | 54 | 55 | 56 | 82 | 83 | 84 | 85 | 86 | # RS-485 Modbus-RTU Call Response and HMI Display with CRC for STM32 87 | 88 | # About the project 89 | 90 | The STM32 NUCLEO-F446RE board is used to communicate with the external device through RS-485 Modbus-RTU. 91 | The device responses are resolved by the STM32 NUCLEO-F446RE with cyclic redundancy check (CRC) and outputted to the human-machine interface (HMI). 92 | 93 | 94 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 95 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](code_of_conduct.md) 96 | 97 | 98 | # Brief description 99 | 100 | There are two boards used in this project, i.e., the main and test boards. 101 | The main board is the major developing object in this project, providing all required functionalities. 102 | The test board is used to simulate the device for testing the functionalities of the main board. 103 | 104 | ## Main board 105 | 106 | Suggested board: 107 | 1. STM32 NUCLEO-F446RE 108 | 1. Blue Pill (STM32F103C8T6) 109 | 110 | Suggested development environment: 111 | 1. STM32CubeIDE 112 | 113 | Purpose: 114 | 1. Send Modbus-RTU command to the device through RS-485 via the UART1. 115 | 1. Get responses from the device through RS-485. 116 | 1. Send the device responses to the human-machine interface (HMI) via the UART3. 117 | 1. Users can monitor the device responses from the STM32CubeIDE console monitor (or other serial monitor) via UART2. 118 | 119 | Suggested hardware setup: 120 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 121 | 1. UART3: A HMI with the UART interface is used to show the device responses through the UART3. 122 | 123 | Suggested software (STM32CubeIDE) setup: 124 | 1. Pinout & configuration/Connectivity 125 | 1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 126 | 1. Turn on USART2, Mode: Asynchronous, Basic parameters: 115200 8N1 127 | 1. Turn on USART3, Mode: Asynchronous, Basic parameters: 115200 8N1 128 | 129 | Suggested library for calculating CRC: 130 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 131 | 1. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 132 | 1. The key files of the library used in this project is put in the [library](library). 133 | 134 | Notice: 135 | 136 | If the following problems happen during compiling by STM32CubeIDE, go to "Project > Properties > C/C++ Build > Settings > Tool Settings > MCU Settings" 137 | and then check the box "Use float with printf from newlib-nano (-u _printf_float)." 138 | 139 | 1. The float formatting support is not enabled, check your MCU Settings from "Project Properties > C/C++ Build > Settings > Tool Settings", or add manually "-u _printf_float" in linker flags. main.c /F103C8_UART_HMI/Core/Src 140 | 2. Problem description: The float formatting support is not enabled, check your MCU Settings from "Project Properties > C/C++ Build > Settings > Tool Settings", or add manually "-u _printf_float" in linker flags. 141 | 142 | 143 | 144 | 145 | ## Test board (device simulator) 146 | 147 | Suggested board: 148 | 1. STM32 NUCLEO-F446RE 149 | 1. Blue Pill (STM32F103C8T6) 150 | 151 | Suggested development environment: 152 | 1. STM32CubeIDE 153 | 154 | Purpose: 155 | 1. Receive Modbus-RTU command from the main board through RS-485 via the UART1. 156 | 1. Generate the Modbus data with CRC and counting value. 157 | 1. Send the Modbus data back. 158 | 159 | Suggested hardware setup: 160 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 161 | 162 | Suggested software (STM32CubeIDE) setup: 163 | 1. Pinout & configuration/Connectivity 164 | 1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 165 | 166 | Suggested library for calculating CRC: 167 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 168 | 1. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 169 | 1. The key files of the library used in this project is put in the [library](library). 170 | 171 | 172 | 173 | 174 | 175 | 176 | 182 | 183 | 184 | 185 | # Usage 186 | 1. Setup the "Pinout & configuration" of the boards by STM32CubeIDE. 187 | 1. Copy and paste the source code into the file ProjectFolder/Core/Src/main.c . 188 | 189 | The source codes are in the [code](code) directory. 190 | Example: "Main board - STM32 NUCLEO-F446RE" means that the STM32 NUCLEO-F446RE is used as the main board. 191 | 192 | ## Main board - Blue Pill (STM32F103C8T6) 193 | 194 | | # | Source file | Date | 195 | | ---- | -------------------------------------------------------------------------------------------------- | ---------- | 196 | | 2 | [Main_Modbus_HMI_BluePill_v02.c](code/Main_board-Blue_Pill/Main_Modbus_HMI_BluePill_v02.c) | 2021-09-17 | 197 | | 1 | [Main_Modbus_HMI_BluePill_v01.c](code/Main_board-Blue_Pill/Main_Modbus_HMI_BluePill_v01.c) | 2021-09-15 | 198 | 199 | 200 | ## Main board - STM32 NUCLEO-F446RE 201 | 202 | | # | Source file | Date | 203 | | ---- | -------------------------------------------------------------------------------------------------- | ---------- | 204 | | 2 | [Main_Modbus_HMI_F446RE_v02.c](code/Main_board-STM32_NUCLEO-F446RE/Main_Modbus_HMI_F446RE_v02.c) | 2021-09-17 | 205 | | 1 | [Main_Modbus_HMI_F446RE_v01.c](code/Main_board-STM32_NUCLEO-F446RE/Main_Modbus_HMI_F446RE_v01.c) | 2021-09-14 | 206 | 207 | 208 | ## Test board - Blue Pill (STM32F103C8T6) 209 | 210 | | # | Source file | Date | 211 | | ---- | -------------------------------------------------------------------------------------------------- | ---------- | 212 | | 1 | [Test_Modbus_HMI_BluePill_v01.c](code/Test_board-Blue_Pill/Test_Modbus_HMI_BluePill_v01.c) | 2021-09-14 | 213 | 214 | 215 | ## Test board - STM32 NUCLEO-F446RE 216 | 217 | | # | Source file | Date | 218 | | ---- | -------------------------------------------------------------------------------------------------- | ---------- | 219 | | 1 | [Test_Modbus_HMI_F446RE_v01.c](code/Test_board-STM32_NUCLEO-F446RE/Test_Modbus_HMI_F446RE_v01.c) | 2021-09-15 | 220 | 221 | 222 | 223 | # License 224 | 225 | Distributed under the [MIT License](LICENSE). 226 | 227 | 228 | 229 | 230 | # Contact 231 | 232 | Author: Dr. Hsien-Ching Chung 233 | 234 | ORCID: https://orcid.org/0000-0001-9364-8858 235 | 236 | Project Link: [https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32](https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32) 237 | 238 | 239 | 240 | 241 | # Acknowledgements 242 | 255 | H.C. Chung thanks all the contributors to this project for their valuable discussions and recommendations, especially Jung-Feng Lin, Hsiao-Wen Yang, Yen-Kai Lo, An-De Andrew Chung. 256 | 257 | This work was supported in part by Super Double Power Technology Co., Ltd., Taiwan under grant SDP-RD-PROJ-001-2020. 258 | 259 | 260 | 261 | 262 | 263 | 278 | -------------------------------------------------------------------------------- /code/Main_board-Blue_Pill/Main_Modbus_HMI_BluePill_v01.c: -------------------------------------------------------------------------------- 1 | /* USER CODE BEGIN Header */ 2 | 3 | /* 4 | Name: Main_Modbus_HMI_BluePill_v01.c 5 | Suggested board: Blue Pill (STM32F103C8T6) 6 | Suggested development environment: STM32CubeIDE 7 | 8 | Purpose: 9 | 1. Send Modbus-RTU command to the device through RS-485 via the UART1. 10 | 2. Get responses from the device through RS-485. 11 | 3. Send the device responses to the human-machine interface (HMI) via the UART3. 12 | 4. Users can monitor the device responses from the serial monitor via UART2. 13 | 14 | Suggested hardware setup: 15 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 16 | 2. UART3: A HMI with the UART interface is used to show the device responses through the UART3. 17 | Suggested software (STM32CubeIDE) setup: 18 | 1. Pinout & configuration/Connectivity 19 | 1.1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 20 | 1.2. Turn on USART2, Mode: Asynchronous, Basic parameters: 115200 8N1 21 | 1.3. Turn on USART3, Mode: Asynchronous, Basic parameters: 115200 8N1 22 | Suggested library for calculating CRC: 23 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 24 | 2. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 25 | 3. The key files of the library used in this project is put in the [library](library). 26 | 27 | Date: 15 Sep. 2021 28 | Author: Dr. Hsien-Ching Chung 29 | ORCID: https://orcid.org/0000-0001-9364-8858 30 | 31 | Project Link: https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32 32 | 33 | License: MIT License 34 | Copyright (c) 2021 Hsien-Ching Chung 35 | */ 36 | 37 | /* USER CODE END Header */ 38 | /* Includes ------------------------------------------------------------------*/ 39 | #include "main.h" 40 | 41 | /* Private includes ----------------------------------------------------------*/ 42 | /* USER CODE BEGIN Includes */ 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include "checksum.h" 48 | // "checksum.h" is the header file for using the crc_modbus() function. 49 | // Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/include/checksum.h 50 | /* USER CODE END Includes */ 51 | 52 | /* Private typedef -----------------------------------------------------------*/ 53 | /* USER CODE BEGIN PTD */ 54 | 55 | /* USER CODE END PTD */ 56 | 57 | /* Private define ------------------------------------------------------------*/ 58 | /* USER CODE BEGIN PD */ 59 | /* USER CODE END PD */ 60 | 61 | /* Private macro -------------------------------------------------------------*/ 62 | /* USER CODE BEGIN PM */ 63 | 64 | /* USER CODE END PM */ 65 | 66 | /* Private variables ---------------------------------------------------------*/ 67 | UART_HandleTypeDef huart1; 68 | UART_HandleTypeDef huart2; 69 | UART_HandleTypeDef huart3; 70 | 71 | /* USER CODE BEGIN PV */ 72 | 73 | /* USER CODE END PV */ 74 | 75 | /* Private function prototypes -----------------------------------------------*/ 76 | void SystemClock_Config(void); 77 | static void MX_GPIO_Init(void); 78 | static void MX_USART1_UART_Init(void); 79 | static void MX_USART2_UART_Init(void); 80 | static void MX_USART3_UART_Init(void); 81 | /* USER CODE BEGIN PFP */ 82 | 83 | /* USER CODE END PFP */ 84 | 85 | /* Private user code ---------------------------------------------------------*/ 86 | /* USER CODE BEGIN 0 */ 87 | // CRC16-related functions 88 | static void init_crc16_tab(void); 89 | static bool crc_tab16_init = false; 90 | static uint16_t crc_tab16[256]; 91 | long map(); 92 | /* USER CODE END 0 */ 93 | 94 | /** 95 | * @brief The application entry point. 96 | * @retval int 97 | */ 98 | int main(void) 99 | { 100 | /* USER CODE BEGIN 1 */ 101 | uint8_t buf[64]; // Buffer for message. 102 | 103 | // Modbus-RTU Command 104 | uint8_t Command_Modbus[8] = {0x01,0x04,0x30,0x00,0x00,0x0B,0xBE,0xCD}; 105 | // [Modbus address convention] 106 | // The usage of the Modbus address (0x3000 = 12288) in "Command_Modbus[8]" does not follow the traditional Modbus address convention. 107 | // Convention: discrete input numbers (1 bit (off/on), 0 or 1) start with 1 and span from 10001 to 19999. 108 | // The users are suggested to design the system following the address convention (or following the device's address convention). 109 | // Ref: https://en.wikipedia.org/wiki/Modbus 110 | 111 | uint8_t Data_Modbus[27]; // Modbus-RTU Data 112 | 113 | // Device responses to Command_Modbus 114 | uint16_t VAC_out; 115 | float VAC_out_f; 116 | uint16_t V_Batt; 117 | float V_Batt_f; 118 | uint16_t Load_percent; 119 | uint16_t Load_power; 120 | 121 | uint8_t EndHex[3] = {0xFF,0xFF,0xFF}; // End command for HMI 122 | /* USER CODE END 1 */ 123 | 124 | /* MCU Configuration--------------------------------------------------------*/ 125 | 126 | /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 127 | HAL_Init(); 128 | 129 | /* USER CODE BEGIN Init */ 130 | 131 | /* USER CODE END Init */ 132 | 133 | /* Configure the system clock */ 134 | SystemClock_Config(); 135 | 136 | /* USER CODE BEGIN SysInit */ 137 | 138 | /* USER CODE END SysInit */ 139 | 140 | /* Initialize all configured peripherals */ 141 | MX_GPIO_Init(); 142 | MX_USART1_UART_Init(); 143 | MX_USART2_UART_Init(); 144 | MX_USART3_UART_Init(); 145 | /* USER CODE BEGIN 2 */ 146 | 147 | /* USER CODE END 2 */ 148 | 149 | /* Infinite loop */ 150 | /* USER CODE BEGIN WHILE */ 151 | while (1) 152 | { 153 | HAL_UART_Transmit(&huart1, Command_Modbus, sizeof(Command_Modbus), 100); // Send Modbus command through UART1 154 | if( HAL_UART_Receive(&huart1, Data_Modbus, sizeof(Data_Modbus), 2000) == HAL_OK ) { // When receiving data from UART1 successfully 155 | 156 | // Send the received Modbus data to serial monitor through UART2 157 | HAL_UART_Transmit(&huart2, Data_Modbus, sizeof(Data_Modbus), 100); 158 | 159 | // Get the CRC16 of the received Modbus data 160 | uint16_t CRC16_data = ( Data_Modbus[sizeof(Data_Modbus)-1] << 8 ) | Data_Modbus[sizeof(Data_Modbus)-2]; 161 | 162 | // Calculate the CRC16 of the received Modbus data by the function 163 | // crc_modbus(const unsigned char *input_str, size_t num_bytes) 164 | uint16_t CRC16_cal = crc_modbus( Data_Modbus, sizeof(Data_Modbus)-2 ); 165 | 166 | if ( CRC16_data == CRC16_cal ) { // When CRC16 check is passed successfully 167 | 168 | // Send message to serial monitor through UART2 169 | strcpy((char*)buf, "CRC16 PASSED.\r\n"); 170 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 171 | 172 | uint16_t value = (Data_Modbus[5] << 8) | Data_Modbus[6]; // Combine/Merge two bytes into one 173 | VAC_out = value; 174 | VAC_out_f = (float)value / 10.0; // Resolve the value 175 | 176 | sprintf((char*)buf, "VAC_out(V): %f\r\n", VAC_out_f); // Show the value on the serial monitor 177 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 178 | 179 | sprintf((char*)buf, "x1.val=%d", VAC_out); // Send device response to HMI 180 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 181 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 182 | 183 | 184 | value = (Data_Modbus[7] << 8) | Data_Modbus[8]; // Combine/Merge two bytes into one 185 | V_Batt = value; 186 | V_Batt_f = (float)value / 10.0; // Resolve the value 187 | 188 | sprintf((char*)buf, "V_Batt(V): %f\r\n", V_Batt_f); // Show the value on the serial monitor 189 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 190 | 191 | sprintf((char*)buf, "x0.val=%d", V_Batt); // Send device response to HMI 192 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 193 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 194 | 195 | 196 | Load_percent = Data_Modbus[12]; // Resolve the value 197 | 198 | sprintf((char*)buf, "Load_percent(%%): %d\r\n", Load_percent); // Show the value on the serial monitor 199 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 200 | 201 | sprintf((char*)buf, "n0.val=%d", Load_percent); // Send device response to HMI 202 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 203 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 204 | 205 | 206 | value = (Data_Modbus[13] << 8) | Data_Modbus[14]; // Combine/Merge two bytes into one 207 | Load_power = value; // Resolve the value 208 | 209 | sprintf((char*)buf, "Load_power(W): %d\r\n", Load_power); // Show the value on the serial monitor 210 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 211 | 212 | int HMI_Pointer = map(Load_power, 0, 2000, 0, 240); // map(value, fromLow, fromHigh, toLow, toHigh) 213 | 214 | sprintf((char*)buf, "z0.val=%d", HMI_Pointer); // Send device response to HMI 215 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 216 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 217 | 218 | } else { 219 | // Send message to serial monitor through UART2 220 | strcpy((char*)buf, "CRC16 FAILED.\r\n"); 221 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 222 | } 223 | } else { 224 | // Send message to serial monitor through UART2 225 | strcpy((char*)buf, "Data receive FAILED. Resend Modbus command.\r\n"); 226 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 227 | } 228 | 229 | //HAL_Delay(50); // Data processing rate control 230 | 231 | /* USER CODE END WHILE */ 232 | 233 | /* USER CODE BEGIN 3 */ 234 | } 235 | /* USER CODE END 3 */ 236 | } 237 | 238 | /** 239 | * @brief System Clock Configuration 240 | * @retval None 241 | */ 242 | void SystemClock_Config(void) 243 | { 244 | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 245 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 246 | 247 | /** Initializes the RCC Oscillators according to the specified parameters 248 | * in the RCC_OscInitTypeDef structure. 249 | */ 250 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 251 | RCC_OscInitStruct.HSIState = RCC_HSI_ON; 252 | RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 253 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; 254 | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 255 | { 256 | Error_Handler(); 257 | } 258 | /** Initializes the CPU, AHB and APB buses clocks 259 | */ 260 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 261 | |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 262 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; 263 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 264 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; 265 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 266 | 267 | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) 268 | { 269 | Error_Handler(); 270 | } 271 | } 272 | 273 | /** 274 | * @brief USART1 Initialization Function 275 | * @param None 276 | * @retval None 277 | */ 278 | static void MX_USART1_UART_Init(void) 279 | { 280 | 281 | /* USER CODE BEGIN USART1_Init 0 */ 282 | 283 | /* USER CODE END USART1_Init 0 */ 284 | 285 | /* USER CODE BEGIN USART1_Init 1 */ 286 | 287 | /* USER CODE END USART1_Init 1 */ 288 | huart1.Instance = USART1; 289 | huart1.Init.BaudRate = 9600; 290 | huart1.Init.WordLength = UART_WORDLENGTH_8B; 291 | huart1.Init.StopBits = UART_STOPBITS_1; 292 | huart1.Init.Parity = UART_PARITY_NONE; 293 | huart1.Init.Mode = UART_MODE_TX_RX; 294 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 295 | huart1.Init.OverSampling = UART_OVERSAMPLING_16; 296 | if (HAL_UART_Init(&huart1) != HAL_OK) 297 | { 298 | Error_Handler(); 299 | } 300 | /* USER CODE BEGIN USART1_Init 2 */ 301 | 302 | /* USER CODE END USART1_Init 2 */ 303 | 304 | } 305 | 306 | /** 307 | * @brief USART2 Initialization Function 308 | * @param None 309 | * @retval None 310 | */ 311 | static void MX_USART2_UART_Init(void) 312 | { 313 | 314 | /* USER CODE BEGIN USART2_Init 0 */ 315 | 316 | /* USER CODE END USART2_Init 0 */ 317 | 318 | /* USER CODE BEGIN USART2_Init 1 */ 319 | 320 | /* USER CODE END USART2_Init 1 */ 321 | huart2.Instance = USART2; 322 | huart2.Init.BaudRate = 115200; 323 | huart2.Init.WordLength = UART_WORDLENGTH_8B; 324 | huart2.Init.StopBits = UART_STOPBITS_1; 325 | huart2.Init.Parity = UART_PARITY_NONE; 326 | huart2.Init.Mode = UART_MODE_TX_RX; 327 | huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; 328 | huart2.Init.OverSampling = UART_OVERSAMPLING_16; 329 | if (HAL_UART_Init(&huart2) != HAL_OK) 330 | { 331 | Error_Handler(); 332 | } 333 | /* USER CODE BEGIN USART2_Init 2 */ 334 | 335 | /* USER CODE END USART2_Init 2 */ 336 | 337 | } 338 | 339 | /** 340 | * @brief USART3 Initialization Function 341 | * @param None 342 | * @retval None 343 | */ 344 | static void MX_USART3_UART_Init(void) 345 | { 346 | 347 | /* USER CODE BEGIN USART3_Init 0 */ 348 | 349 | /* USER CODE END USART3_Init 0 */ 350 | 351 | /* USER CODE BEGIN USART3_Init 1 */ 352 | 353 | /* USER CODE END USART3_Init 1 */ 354 | huart3.Instance = USART3; 355 | huart3.Init.BaudRate = 115200; 356 | huart3.Init.WordLength = UART_WORDLENGTH_8B; 357 | huart3.Init.StopBits = UART_STOPBITS_1; 358 | huart3.Init.Parity = UART_PARITY_NONE; 359 | huart3.Init.Mode = UART_MODE_TX_RX; 360 | huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; 361 | huart3.Init.OverSampling = UART_OVERSAMPLING_16; 362 | if (HAL_UART_Init(&huart3) != HAL_OK) 363 | { 364 | Error_Handler(); 365 | } 366 | /* USER CODE BEGIN USART3_Init 2 */ 367 | 368 | /* USER CODE END USART3_Init 2 */ 369 | 370 | } 371 | 372 | /** 373 | * @brief GPIO Initialization Function 374 | * @param None 375 | * @retval None 376 | */ 377 | static void MX_GPIO_Init(void) 378 | { 379 | 380 | /* GPIO Ports Clock Enable */ 381 | __HAL_RCC_GPIOA_CLK_ENABLE(); 382 | __HAL_RCC_GPIOB_CLK_ENABLE(); 383 | 384 | } 385 | 386 | /* USER CODE BEGIN 4 */ 387 | 388 | /* 389 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 390 | 391 | The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 392 | a byte string of which the beginning has been passed to the function. The 393 | number of bytes to check is also a parameter. 394 | 395 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 396 | */ 397 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes) { 398 | 399 | uint16_t crc; 400 | const unsigned char *ptr; 401 | size_t a; 402 | 403 | if (!crc_tab16_init) init_crc16_tab(); 404 | 405 | crc = CRC_START_MODBUS; 406 | ptr = input_str; 407 | 408 | if (ptr != NULL) for (a = 0; a < num_bytes; a++) { 409 | 410 | crc = (crc >> 8) ^ crc_tab16[(crc ^ (uint16_t)*ptr++) & 0x00FF]; 411 | } 412 | 413 | return crc; 414 | 415 | } /* crc_modbus */ 416 | 417 | /* 418 | static void init_crc16_tab( void ); 419 | 420 | For optimal performance uses the CRC16 routine a lookup table with values 421 | that can be used directly in the XOR arithmetic in the algorithm. This 422 | lookup table is calculated by the init_crc16_tab() routine, the first time 423 | the CRC function is called. 424 | 425 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 426 | */ 427 | static void init_crc16_tab(void) { 428 | 429 | uint16_t i; 430 | uint16_t j; 431 | uint16_t crc; 432 | uint16_t c; 433 | 434 | for (i = 0; i < 256; i++) { 435 | 436 | crc = 0; 437 | c = i; 438 | 439 | for (j = 0; j < 8; j++) { 440 | 441 | if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16; 442 | else crc = crc >> 1; 443 | 444 | c = c >> 1; 445 | } 446 | 447 | crc_tab16[i] = crc; 448 | } 449 | 450 | crc_tab16_init = true; 451 | 452 | } /* init_crc16_tab */ 453 | 454 | // map(value, fromLow, fromHigh, toLow, toHigh) 455 | long map(long x, long in_min, long in_max, long out_min, long out_max) { 456 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 457 | } 458 | 459 | /* USER CODE END 4 */ 460 | 461 | /** 462 | * @brief This function is executed in case of error occurrence. 463 | * @retval None 464 | */ 465 | void Error_Handler(void) 466 | { 467 | /* USER CODE BEGIN Error_Handler_Debug */ 468 | /* User can add his own implementation to report the HAL error return state */ 469 | __disable_irq(); 470 | while (1) 471 | { 472 | } 473 | /* USER CODE END Error_Handler_Debug */ 474 | } 475 | 476 | #ifdef USE_FULL_ASSERT 477 | /** 478 | * @brief Reports the name of the source file and the source line number 479 | * where the assert_param error has occurred. 480 | * @param file: pointer to the source file name 481 | * @param line: assert_param error line source number 482 | * @retval None 483 | */ 484 | void assert_failed(uint8_t *file, uint32_t line) 485 | { 486 | /* USER CODE BEGIN 6 */ 487 | /* User can add his own implementation to report the file name and line number, 488 | ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 489 | /* USER CODE END 6 */ 490 | } 491 | #endif /* USE_FULL_ASSERT */ 492 | -------------------------------------------------------------------------------- /code/Main_board-Blue_Pill/Main_Modbus_HMI_BluePill_v02.c: -------------------------------------------------------------------------------- 1 | /* USER CODE BEGIN Header */ 2 | 3 | /* 4 | Name: Main_Modbus_HMI_BluePill_v02.c 5 | Suggested board: Blue Pill (STM32F103C8T6) 6 | Suggested development environment: STM32CubeIDE 7 | 8 | Purpose: 9 | 1. Send Modbus-RTU command to the device through RS-485 via the UART1. 10 | 2. Get responses from the device through RS-485. 11 | 3. Send the device responses to the human-machine interface (HMI) via the UART3. 12 | 4. Users can monitor the device responses from the serial monitor via UART2. 13 | 14 | Suggested hardware setup: 15 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 16 | 2. UART3: A HMI with the UART interface is used to show the device responses through the UART3. 17 | Suggested software (STM32CubeIDE) setup: 18 | 1. Pinout & configuration/Connectivity 19 | 1.1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 20 | 1.2. Turn on USART2, Mode: Asynchronous, Basic parameters: 115200 8N1 21 | 1.3. Turn on USART3, Mode: Asynchronous, Basic parameters: 115200 8N1 22 | Suggested library for calculating CRC: 23 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 24 | 2. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 25 | 3. The key files of the library used in this project is put in the [library](library). 26 | 27 | Date: 17 Sep. 2021 28 | Author: Dr. Hsien-Ching Chung 29 | ORCID: https://orcid.org/0000-0001-9364-8858 30 | 31 | Project Link: https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32 32 | 33 | License: MIT License 34 | Copyright (c) 2021 Hsien-Ching Chung 35 | ------------------------------------ 36 | Update description: 37 | v02 2021-09-17 38 | 1. Show Modbus data in string format through UART2. 39 | 2. Send the message "Data receiving SUCCESS." through UART2 after receiving the Modbus data. 40 | 3. Correct some tyops in the comments. 41 | v01 2021-09-14 42 | Initial version. 43 | */ 44 | 45 | /* USER CODE END Header */ 46 | /* Includes ------------------------------------------------------------------*/ 47 | #include "main.h" 48 | 49 | /* Private includes ----------------------------------------------------------*/ 50 | /* USER CODE BEGIN Includes */ 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include "checksum.h" 56 | // "checksum.h" is the header file for using the crc_modbus() function. 57 | // Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/include/checksum.h 58 | /* USER CODE END Includes */ 59 | 60 | /* Private typedef -----------------------------------------------------------*/ 61 | /* USER CODE BEGIN PTD */ 62 | 63 | /* USER CODE END PTD */ 64 | 65 | /* Private define ------------------------------------------------------------*/ 66 | /* USER CODE BEGIN PD */ 67 | /* USER CODE END PD */ 68 | 69 | /* Private macro -------------------------------------------------------------*/ 70 | /* USER CODE BEGIN PM */ 71 | 72 | /* USER CODE END PM */ 73 | 74 | /* Private variables ---------------------------------------------------------*/ 75 | UART_HandleTypeDef huart1; 76 | UART_HandleTypeDef huart2; 77 | UART_HandleTypeDef huart3; 78 | 79 | /* USER CODE BEGIN PV */ 80 | 81 | /* USER CODE END PV */ 82 | 83 | /* Private function prototypes -----------------------------------------------*/ 84 | void SystemClock_Config(void); 85 | static void MX_GPIO_Init(void); 86 | static void MX_USART1_UART_Init(void); 87 | static void MX_USART2_UART_Init(void); 88 | static void MX_USART3_UART_Init(void); 89 | /* USER CODE BEGIN PFP */ 90 | 91 | /* USER CODE END PFP */ 92 | 93 | /* Private user code ---------------------------------------------------------*/ 94 | /* USER CODE BEGIN 0 */ 95 | // CRC16-related functions 96 | static void init_crc16_tab(void); 97 | static bool crc_tab16_init = false; 98 | static uint16_t crc_tab16[256]; 99 | long map(); 100 | /* USER CODE END 0 */ 101 | 102 | /** 103 | * @brief The application entry point. 104 | * @retval int 105 | */ 106 | int main(void) 107 | { 108 | /* USER CODE BEGIN 1 */ 109 | uint8_t buf[64]; // Buffer for message. 110 | 111 | // Modbus-RTU Command 112 | uint8_t Command_Modbus[8] = {0x01,0x04,0x30,0x00,0x00,0x0B,0xBE,0xCD}; 113 | // [Modbus address convention] 114 | // The usage of the Modbus address (0x3000 = 12288) in "Command_Modbus[8]" does not follow the traditional Modbus address convention. 115 | // Convention: discrete input numbers (1 bit (off/on), 0 or 1) start with 1 and span from 10001 to 19999. 116 | // The users are suggested to design the system following the address convention (or following the device's address convention). 117 | // Ref: https://en.wikipedia.org/wiki/Modbus 118 | 119 | uint8_t Data_Modbus[27]; // Modbus-RTU Data 120 | 121 | // Device responses to Command_Modbus 122 | uint16_t VAC_out; 123 | float VAC_out_f; 124 | uint16_t V_Batt; 125 | float V_Batt_f; 126 | uint16_t Load_percent; 127 | uint16_t Load_power; 128 | 129 | uint8_t EndHex[3] = {0xFF,0xFF,0xFF}; // End command for HMI 130 | /* USER CODE END 1 */ 131 | 132 | /* MCU Configuration--------------------------------------------------------*/ 133 | 134 | /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 135 | HAL_Init(); 136 | 137 | /* USER CODE BEGIN Init */ 138 | 139 | /* USER CODE END Init */ 140 | 141 | /* Configure the system clock */ 142 | SystemClock_Config(); 143 | 144 | /* USER CODE BEGIN SysInit */ 145 | 146 | /* USER CODE END SysInit */ 147 | 148 | /* Initialize all configured peripherals */ 149 | MX_GPIO_Init(); 150 | MX_USART1_UART_Init(); 151 | MX_USART2_UART_Init(); 152 | MX_USART3_UART_Init(); 153 | /* USER CODE BEGIN 2 */ 154 | 155 | /* USER CODE END 2 */ 156 | 157 | /* Infinite loop */ 158 | /* USER CODE BEGIN WHILE */ 159 | while (1) 160 | { 161 | HAL_UART_Transmit(&huart1, Command_Modbus, sizeof(Command_Modbus), 100); // Send Modbus command through UART1 162 | if( HAL_UART_Receive(&huart1, Data_Modbus, sizeof(Data_Modbus), 2000) == HAL_OK ) { // When receiving data from UART1 successfully 163 | 164 | // Send message to serial monitor through UART2 165 | strcpy((char*)buf, "Data receiving SUCCESS.\r\n"); 166 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 167 | 168 | //Show the received Modbus data on the serial monitor through UART2 169 | strcpy((char*)buf, "Received Modbus data:\r\n"); 170 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 171 | 172 | for (int i = 0; i < sizeof(Data_Modbus); i++) { 173 | // Show the value on the serial monitor 174 | sprintf((char*)buf, " %02X", Data_Modbus[i]); 175 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 176 | } 177 | 178 | strcpy((char*)buf, "\r\n"); 179 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 180 | 181 | 182 | // Get the CRC16 of the received Modbus data 183 | uint16_t CRC16_data = ( Data_Modbus[sizeof(Data_Modbus)-1] << 8 ) | Data_Modbus[sizeof(Data_Modbus)-2]; 184 | 185 | // Calculate the CRC16 of the received Modbus data by the function 186 | // crc_modbus(const unsigned char *input_str, size_t num_bytes) 187 | uint16_t CRC16_cal = crc_modbus( Data_Modbus, sizeof(Data_Modbus)-2 ); 188 | 189 | 190 | if ( CRC16_data == CRC16_cal ) { // When CRC16 check is passed successfully 191 | 192 | // Send message to serial monitor through UART2 193 | strcpy((char*)buf, "CRC16 check PASSED.\r\n"); 194 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 195 | 196 | uint16_t value = (Data_Modbus[5] << 8) | Data_Modbus[6]; // Combine/Merge two bytes into one 197 | VAC_out = value; 198 | VAC_out_f = (float)value / 10.0; // Resolve the value 199 | 200 | sprintf((char*)buf, "VAC_out(V): %f\r\n", VAC_out_f); // Show the value on the serial monitor 201 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 202 | 203 | sprintf((char*)buf, "x1.val=%d", VAC_out); // Send device response to HMI 204 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 205 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 206 | 207 | 208 | value = (Data_Modbus[7] << 8) | Data_Modbus[8]; // Combine/Merge two bytes into one 209 | V_Batt = value; 210 | V_Batt_f = (float)value / 10.0; // Resolve the value 211 | 212 | sprintf((char*)buf, "V_Batt(V): %f\r\n", V_Batt_f); // Show the value on the serial monitor 213 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 214 | 215 | sprintf((char*)buf, "x0.val=%d", V_Batt); // Send device response to HMI 216 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 217 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 218 | 219 | 220 | Load_percent = Data_Modbus[12]; // Resolve the value 221 | 222 | sprintf((char*)buf, "Load_percent(%%): %d\r\n", Load_percent); // Show the value on the serial monitor 223 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 224 | 225 | sprintf((char*)buf, "n0.val=%d", Load_percent); // Send device response to HMI 226 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 227 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 228 | 229 | 230 | value = (Data_Modbus[13] << 8) | Data_Modbus[14]; // Combine/Merge two bytes into one 231 | Load_power = value; // Resolve the value 232 | 233 | sprintf((char*)buf, "Load_power(W): %d\r\n", Load_power); // Show the value on the serial monitor 234 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 235 | 236 | int HMI_Pointer = map(Load_power, 0, 2000, 0, 240); // map(value, fromLow, fromHigh, toLow, toHigh) 237 | 238 | sprintf((char*)buf, "z0.val=%d", HMI_Pointer); // Send device response to HMI 239 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 240 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 241 | 242 | } else { 243 | // Send message to serial monitor through UART2 244 | strcpy((char*)buf, "CRC16 check FAILED.\r\n"); 245 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 246 | } 247 | } else { 248 | // Send message to serial monitor through UART2 249 | strcpy((char*)buf, "Data receiving FAILED. Resend Modbus command.\r\n"); 250 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 251 | } 252 | 253 | //HAL_Delay(50); // Data processing rate control 254 | 255 | /* USER CODE END WHILE */ 256 | 257 | /* USER CODE BEGIN 3 */ 258 | } 259 | /* USER CODE END 3 */ 260 | } 261 | 262 | /** 263 | * @brief System Clock Configuration 264 | * @retval None 265 | */ 266 | void SystemClock_Config(void) 267 | { 268 | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 269 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 270 | 271 | /** Initializes the RCC Oscillators according to the specified parameters 272 | * in the RCC_OscInitTypeDef structure. 273 | */ 274 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 275 | RCC_OscInitStruct.HSIState = RCC_HSI_ON; 276 | RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 277 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; 278 | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 279 | { 280 | Error_Handler(); 281 | } 282 | /** Initializes the CPU, AHB and APB buses clocks 283 | */ 284 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 285 | |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 286 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; 287 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 288 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; 289 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 290 | 291 | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) 292 | { 293 | Error_Handler(); 294 | } 295 | } 296 | 297 | /** 298 | * @brief USART1 Initialization Function 299 | * @param None 300 | * @retval None 301 | */ 302 | static void MX_USART1_UART_Init(void) 303 | { 304 | 305 | /* USER CODE BEGIN USART1_Init 0 */ 306 | 307 | /* USER CODE END USART1_Init 0 */ 308 | 309 | /* USER CODE BEGIN USART1_Init 1 */ 310 | 311 | /* USER CODE END USART1_Init 1 */ 312 | huart1.Instance = USART1; 313 | huart1.Init.BaudRate = 9600; 314 | huart1.Init.WordLength = UART_WORDLENGTH_8B; 315 | huart1.Init.StopBits = UART_STOPBITS_1; 316 | huart1.Init.Parity = UART_PARITY_NONE; 317 | huart1.Init.Mode = UART_MODE_TX_RX; 318 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 319 | huart1.Init.OverSampling = UART_OVERSAMPLING_16; 320 | if (HAL_UART_Init(&huart1) != HAL_OK) 321 | { 322 | Error_Handler(); 323 | } 324 | /* USER CODE BEGIN USART1_Init 2 */ 325 | 326 | /* USER CODE END USART1_Init 2 */ 327 | 328 | } 329 | 330 | /** 331 | * @brief USART2 Initialization Function 332 | * @param None 333 | * @retval None 334 | */ 335 | static void MX_USART2_UART_Init(void) 336 | { 337 | 338 | /* USER CODE BEGIN USART2_Init 0 */ 339 | 340 | /* USER CODE END USART2_Init 0 */ 341 | 342 | /* USER CODE BEGIN USART2_Init 1 */ 343 | 344 | /* USER CODE END USART2_Init 1 */ 345 | huart2.Instance = USART2; 346 | huart2.Init.BaudRate = 115200; 347 | huart2.Init.WordLength = UART_WORDLENGTH_8B; 348 | huart2.Init.StopBits = UART_STOPBITS_1; 349 | huart2.Init.Parity = UART_PARITY_NONE; 350 | huart2.Init.Mode = UART_MODE_TX_RX; 351 | huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; 352 | huart2.Init.OverSampling = UART_OVERSAMPLING_16; 353 | if (HAL_UART_Init(&huart2) != HAL_OK) 354 | { 355 | Error_Handler(); 356 | } 357 | /* USER CODE BEGIN USART2_Init 2 */ 358 | 359 | /* USER CODE END USART2_Init 2 */ 360 | 361 | } 362 | 363 | /** 364 | * @brief USART3 Initialization Function 365 | * @param None 366 | * @retval None 367 | */ 368 | static void MX_USART3_UART_Init(void) 369 | { 370 | 371 | /* USER CODE BEGIN USART3_Init 0 */ 372 | 373 | /* USER CODE END USART3_Init 0 */ 374 | 375 | /* USER CODE BEGIN USART3_Init 1 */ 376 | 377 | /* USER CODE END USART3_Init 1 */ 378 | huart3.Instance = USART3; 379 | huart3.Init.BaudRate = 115200; 380 | huart3.Init.WordLength = UART_WORDLENGTH_8B; 381 | huart3.Init.StopBits = UART_STOPBITS_1; 382 | huart3.Init.Parity = UART_PARITY_NONE; 383 | huart3.Init.Mode = UART_MODE_TX_RX; 384 | huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; 385 | huart3.Init.OverSampling = UART_OVERSAMPLING_16; 386 | if (HAL_UART_Init(&huart3) != HAL_OK) 387 | { 388 | Error_Handler(); 389 | } 390 | /* USER CODE BEGIN USART3_Init 2 */ 391 | 392 | /* USER CODE END USART3_Init 2 */ 393 | 394 | } 395 | 396 | /** 397 | * @brief GPIO Initialization Function 398 | * @param None 399 | * @retval None 400 | */ 401 | static void MX_GPIO_Init(void) 402 | { 403 | 404 | /* GPIO Ports Clock Enable */ 405 | __HAL_RCC_GPIOA_CLK_ENABLE(); 406 | __HAL_RCC_GPIOB_CLK_ENABLE(); 407 | 408 | } 409 | 410 | /* USER CODE BEGIN 4 */ 411 | 412 | /* 413 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 414 | 415 | The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 416 | a byte string of which the beginning has been passed to the function. The 417 | number of bytes to check is also a parameter. 418 | 419 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 420 | */ 421 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes) { 422 | 423 | uint16_t crc; 424 | const unsigned char *ptr; 425 | size_t a; 426 | 427 | if (!crc_tab16_init) init_crc16_tab(); 428 | 429 | crc = CRC_START_MODBUS; 430 | ptr = input_str; 431 | 432 | if (ptr != NULL) for (a = 0; a < num_bytes; a++) { 433 | 434 | crc = (crc >> 8) ^ crc_tab16[(crc ^ (uint16_t)*ptr++) & 0x00FF]; 435 | } 436 | 437 | return crc; 438 | 439 | } /* crc_modbus */ 440 | 441 | /* 442 | static void init_crc16_tab( void ); 443 | 444 | For optimal performance uses the CRC16 routine a lookup table with values 445 | that can be used directly in the XOR arithmetic in the algorithm. This 446 | lookup table is calculated by the init_crc16_tab() routine, the first time 447 | the CRC function is called. 448 | 449 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 450 | */ 451 | static void init_crc16_tab(void) { 452 | 453 | uint16_t i; 454 | uint16_t j; 455 | uint16_t crc; 456 | uint16_t c; 457 | 458 | for (i = 0; i < 256; i++) { 459 | 460 | crc = 0; 461 | c = i; 462 | 463 | for (j = 0; j < 8; j++) { 464 | 465 | if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16; 466 | else crc = crc >> 1; 467 | 468 | c = c >> 1; 469 | } 470 | 471 | crc_tab16[i] = crc; 472 | } 473 | 474 | crc_tab16_init = true; 475 | 476 | } /* init_crc16_tab */ 477 | 478 | // map(value, fromLow, fromHigh, toLow, toHigh) 479 | long map(long x, long in_min, long in_max, long out_min, long out_max) { 480 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 481 | } 482 | 483 | /* USER CODE END 4 */ 484 | 485 | /** 486 | * @brief This function is executed in case of error occurrence. 487 | * @retval None 488 | */ 489 | void Error_Handler(void) 490 | { 491 | /* USER CODE BEGIN Error_Handler_Debug */ 492 | /* User can add his own implementation to report the HAL error return state */ 493 | __disable_irq(); 494 | while (1) 495 | { 496 | } 497 | /* USER CODE END Error_Handler_Debug */ 498 | } 499 | 500 | #ifdef USE_FULL_ASSERT 501 | /** 502 | * @brief Reports the name of the source file and the source line number 503 | * where the assert_param error has occurred. 504 | * @param file: pointer to the source file name 505 | * @param line: assert_param error line source number 506 | * @retval None 507 | */ 508 | void assert_failed(uint8_t *file, uint32_t line) 509 | { 510 | /* USER CODE BEGIN 6 */ 511 | /* User can add his own implementation to report the file name and line number, 512 | ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 513 | /* USER CODE END 6 */ 514 | } 515 | #endif /* USE_FULL_ASSERT */ 516 | -------------------------------------------------------------------------------- /code/Main_board-STM32_NUCLEO-F446RE/Main_Modbus_HMI_F446RE_v01.c: -------------------------------------------------------------------------------- 1 | /* USER CODE BEGIN Header */ 2 | 3 | /* 4 | Name: Main_Modbus_HMI_F446RE_v01.c 5 | Suggested board: STM32 NUCLEO-F446RE 6 | Suggested development environment: STM32CubeIDE 7 | 8 | Purpose: 9 | 1. Send Modbus-RTU command to the device through RS-485 via the UART1. 10 | 2. Get responses from the device through RS-485. 11 | 3. Send the device responses to the human-machine interface (HMI) via the UART3. 12 | 4. Users can monitor the device responses from the STM32CubeIDE console monitor via UART2. 13 | 14 | Suggested hardware setup: 15 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 16 | 2. UART3: A HMI with the UART interface is used to show the device responses through the UART3. 17 | Suggested software (STM32CubeIDE) setup: 18 | 1. Pinout & configuration/Connectivity 19 | 1.1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 20 | 1.2. Turn on USART2, Mode: Asynchronous, Basic parameters: 115200 8N1 21 | 1.3. Turn on USART3, Mode: Asynchronous, Basic parameters: 115200 8N1 22 | Suggested library for calculating CRC: 23 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 24 | 2. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 25 | 3. The key files of the library used in this project is put in the [library](library). 26 | 27 | Date: 14 Sep. 2021 28 | Author: Dr. Hsien-Ching Chung 29 | ORCID: https://orcid.org/0000-0001-9364-8858 30 | 31 | Project Link: https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32 32 | 33 | License: MIT License 34 | Copyright (c) 2021 Hsien-Ching Chung 35 | */ 36 | 37 | /* USER CODE END Header */ 38 | /* Includes ------------------------------------------------------------------*/ 39 | #include "main.h" 40 | 41 | /* Private includes ----------------------------------------------------------*/ 42 | /* USER CODE BEGIN Includes */ 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include "checksum.h" 48 | // "checksum.h" is the header file for using the crc_modbus() function. 49 | // Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/include/checksum.h 50 | /* USER CODE END Includes */ 51 | 52 | /* Private typedef -----------------------------------------------------------*/ 53 | /* USER CODE BEGIN PTD */ 54 | 55 | /* USER CODE END PTD */ 56 | 57 | /* Private define ------------------------------------------------------------*/ 58 | /* USER CODE BEGIN PD */ 59 | /* USER CODE END PD */ 60 | 61 | /* Private macro -------------------------------------------------------------*/ 62 | /* USER CODE BEGIN PM */ 63 | 64 | /* USER CODE END PM */ 65 | 66 | /* Private variables ---------------------------------------------------------*/ 67 | UART_HandleTypeDef huart1; 68 | UART_HandleTypeDef huart2; 69 | UART_HandleTypeDef huart3; 70 | 71 | /* USER CODE BEGIN PV */ 72 | 73 | /* USER CODE END PV */ 74 | 75 | /* Private function prototypes -----------------------------------------------*/ 76 | void SystemClock_Config(void); 77 | static void MX_GPIO_Init(void); 78 | static void MX_USART2_UART_Init(void); 79 | static void MX_USART1_UART_Init(void); 80 | static void MX_USART3_UART_Init(void); 81 | /* USER CODE BEGIN PFP */ 82 | 83 | /* USER CODE END PFP */ 84 | 85 | /* Private user code ---------------------------------------------------------*/ 86 | /* USER CODE BEGIN 0 */ 87 | // CRC16-related functions 88 | static void init_crc16_tab(void); 89 | static bool crc_tab16_init = false; 90 | static uint16_t crc_tab16[256]; 91 | long map(); 92 | /* USER CODE END 0 */ 93 | 94 | /** 95 | * @brief The application entry point. 96 | * @retval int 97 | */ 98 | int main(void) 99 | { 100 | /* USER CODE BEGIN 1 */ 101 | uint8_t buf[64]; // Buffer for message. 102 | 103 | // Modbus-RTU Command 104 | uint8_t Command_Modbus[8] = {0x01,0x04,0x30,0x00,0x00,0x0B,0xBE,0xCD}; 105 | // [Modbus address convention] 106 | // The usage of the Modbus address (0x3000 = 12288) in "Command_Modbus[8]" does not follow the traditional Modbus address convention. 107 | // Convention: discrete input numbers (1 bit (off/on), 0 or 1) start with 1 and span from 10001 to 19999. 108 | // The users are suggested to design the system following the address convention (or following the device's address convention). 109 | // Ref: https://en.wikipedia.org/wiki/Modbus 110 | 111 | uint8_t Data_Modbus[27]; // Modbus-RTU Data 112 | 113 | // Device responses to Command_Modbus 114 | uint16_t VAC_out; 115 | float VAC_out_f; 116 | uint16_t V_Batt; 117 | float V_Batt_f; 118 | uint16_t Load_percent; 119 | uint16_t Load_power; 120 | 121 | uint8_t EndHex[3] = {0xFF,0xFF,0xFF}; // End command for HMI 122 | /* USER CODE END 1 */ 123 | 124 | /* MCU Configuration--------------------------------------------------------*/ 125 | 126 | /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 127 | HAL_Init(); 128 | 129 | /* USER CODE BEGIN Init */ 130 | 131 | /* USER CODE END Init */ 132 | 133 | /* Configure the system clock */ 134 | SystemClock_Config(); 135 | 136 | /* USER CODE BEGIN SysInit */ 137 | 138 | /* USER CODE END SysInit */ 139 | 140 | /* Initialize all configured peripherals */ 141 | MX_GPIO_Init(); 142 | MX_USART2_UART_Init(); 143 | MX_USART1_UART_Init(); 144 | MX_USART3_UART_Init(); 145 | /* USER CODE BEGIN 2 */ 146 | 147 | /* USER CODE END 2 */ 148 | 149 | /* Infinite loop */ 150 | /* USER CODE BEGIN WHILE */ 151 | while (1) 152 | { 153 | HAL_UART_Transmit(&huart1, Command_Modbus, sizeof(Command_Modbus), 100); // Send Modbus command through UART1 154 | if( HAL_UART_Receive(&huart1, Data_Modbus, sizeof(Data_Modbus), 2000) == HAL_OK ) { // When receiving data from UART1 successfully 155 | 156 | HAL_UART_Transmit(&huart2, Data_Modbus, sizeof(Data_Modbus), 100); // Send the received Modbus data to STM32CubeIDE console monitor through UART2 157 | 158 | // Get the CRC16 of the received Modbus data 159 | uint16_t CRC16_data = ( Data_Modbus[sizeof(Data_Modbus)-1] << 8 ) | Data_Modbus[sizeof(Data_Modbus)-2]; 160 | 161 | // Calculate the CRC16 of the received Modbus data by the function 162 | // crc_modbus(const unsigned char *input_str, size_t num_bytes) 163 | uint16_t CRC16_cal = crc_modbus( Data_Modbus, sizeof(Data_Modbus)-2 ); 164 | 165 | if ( CRC16_data == CRC16_cal ) { // When CRC16 check is passed successfully 166 | 167 | strcpy((char*)buf, "CRC16 PASSED.\r\n"); 168 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 169 | 170 | uint16_t value = (Data_Modbus[5] << 8) | Data_Modbus[6]; // Combine/Merge two bytes into one 171 | VAC_out = value; 172 | VAC_out_f = (float)value / 10.0; // Resolve the value 173 | 174 | sprintf((char*)buf, "VAC_out(V): %f\r\n", VAC_out_f); // Show the value on the serial monitor 175 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 176 | 177 | sprintf((char*)buf, "x1.val=%d", VAC_out); // Send device response to HMI 178 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 179 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 180 | 181 | 182 | value = (Data_Modbus[7] << 8) | Data_Modbus[8]; // Combine/Merge two bytes into one 183 | V_Batt = value; 184 | V_Batt_f = (float)value / 10.0; // Resolve the value 185 | 186 | sprintf((char*)buf, "V_Batt(V): %f\r\n", V_Batt_f); // Show the value on the serial monitor 187 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 188 | 189 | sprintf((char*)buf, "x0.val=%d", V_Batt); // Send device response to HMI 190 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 191 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 192 | 193 | 194 | Load_percent = Data_Modbus[12]; // Resolve the value 195 | 196 | sprintf((char*)buf, "Load_percent(%%): %d\r\n", Load_percent); // Show the value on the serial monitor 197 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 198 | 199 | sprintf((char*)buf, "n0.val=%d", Load_percent); // Send device response to HMI 200 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 201 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 202 | 203 | 204 | value = (Data_Modbus[13] << 8) | Data_Modbus[14]; // Combine/Merge two bytes into one 205 | Load_power = value; // Resolve the value 206 | 207 | sprintf((char*)buf, "Load_power(W): %d\r\n", Load_power); // Show the value on the serial monitor 208 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 209 | 210 | int HMI_Pointer = map(Load_power, 0, 2000, 0, 240); // map(value, fromLow, fromHigh, toLow, toHigh) 211 | 212 | sprintf((char*)buf, "z0.val=%d", HMI_Pointer); // Send device response to HMI 213 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 214 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 215 | 216 | } else { 217 | // Send message to STM32CubeIDE console monitor through UART2 218 | strcpy((char*)buf, "CRC16 FAILED.\r\n"); 219 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 220 | } 221 | } else { 222 | // Send message to STM32CubeIDE console monitor through UART2 223 | strcpy((char*)buf, "Data receive FAILED. Resend Modbus command.\r\n"); 224 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 225 | } 226 | 227 | //HAL_Delay(50); // Data processing rate control 228 | 229 | /* USER CODE END WHILE */ 230 | 231 | /* USER CODE BEGIN 3 */ 232 | } 233 | /* USER CODE END 3 */ 234 | } 235 | 236 | /** 237 | * @brief System Clock Configuration 238 | * @retval None 239 | */ 240 | void SystemClock_Config(void) 241 | { 242 | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 243 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 244 | 245 | /** Configure the main internal regulator output voltage 246 | */ 247 | __HAL_RCC_PWR_CLK_ENABLE(); 248 | __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3); 249 | /** Initializes the RCC Oscillators according to the specified parameters 250 | * in the RCC_OscInitTypeDef structure. 251 | */ 252 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 253 | RCC_OscInitStruct.HSIState = RCC_HSI_ON; 254 | RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 255 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 256 | RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; 257 | RCC_OscInitStruct.PLL.PLLM = 16; 258 | RCC_OscInitStruct.PLL.PLLN = 336; 259 | RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4; 260 | RCC_OscInitStruct.PLL.PLLQ = 2; 261 | RCC_OscInitStruct.PLL.PLLR = 2; 262 | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 263 | { 264 | Error_Handler(); 265 | } 266 | /** Initializes the CPU, AHB and APB buses clocks 267 | */ 268 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 269 | |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 270 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 271 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 272 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; 273 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 274 | 275 | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) 276 | { 277 | Error_Handler(); 278 | } 279 | } 280 | 281 | /** 282 | * @brief USART1 Initialization Function 283 | * @param None 284 | * @retval None 285 | */ 286 | static void MX_USART1_UART_Init(void) 287 | { 288 | 289 | /* USER CODE BEGIN USART1_Init 0 */ 290 | 291 | /* USER CODE END USART1_Init 0 */ 292 | 293 | /* USER CODE BEGIN USART1_Init 1 */ 294 | 295 | /* USER CODE END USART1_Init 1 */ 296 | huart1.Instance = USART1; 297 | huart1.Init.BaudRate = 9600; 298 | huart1.Init.WordLength = UART_WORDLENGTH_8B; 299 | huart1.Init.StopBits = UART_STOPBITS_1; 300 | huart1.Init.Parity = UART_PARITY_NONE; 301 | huart1.Init.Mode = UART_MODE_TX_RX; 302 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 303 | huart1.Init.OverSampling = UART_OVERSAMPLING_16; 304 | if (HAL_UART_Init(&huart1) != HAL_OK) 305 | { 306 | Error_Handler(); 307 | } 308 | /* USER CODE BEGIN USART1_Init 2 */ 309 | 310 | /* USER CODE END USART1_Init 2 */ 311 | 312 | } 313 | 314 | /** 315 | * @brief USART2 Initialization Function 316 | * @param None 317 | * @retval None 318 | */ 319 | static void MX_USART2_UART_Init(void) 320 | { 321 | 322 | /* USER CODE BEGIN USART2_Init 0 */ 323 | 324 | /* USER CODE END USART2_Init 0 */ 325 | 326 | /* USER CODE BEGIN USART2_Init 1 */ 327 | 328 | /* USER CODE END USART2_Init 1 */ 329 | huart2.Instance = USART2; 330 | huart2.Init.BaudRate = 115200; 331 | huart2.Init.WordLength = UART_WORDLENGTH_8B; 332 | huart2.Init.StopBits = UART_STOPBITS_1; 333 | huart2.Init.Parity = UART_PARITY_NONE; 334 | huart2.Init.Mode = UART_MODE_TX_RX; 335 | huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; 336 | huart2.Init.OverSampling = UART_OVERSAMPLING_16; 337 | if (HAL_UART_Init(&huart2) != HAL_OK) 338 | { 339 | Error_Handler(); 340 | } 341 | /* USER CODE BEGIN USART2_Init 2 */ 342 | 343 | /* USER CODE END USART2_Init 2 */ 344 | 345 | } 346 | 347 | /** 348 | * @brief USART3 Initialization Function 349 | * @param None 350 | * @retval None 351 | */ 352 | static void MX_USART3_UART_Init(void) 353 | { 354 | 355 | /* USER CODE BEGIN USART3_Init 0 */ 356 | 357 | /* USER CODE END USART3_Init 0 */ 358 | 359 | /* USER CODE BEGIN USART3_Init 1 */ 360 | 361 | /* USER CODE END USART3_Init 1 */ 362 | huart3.Instance = USART3; 363 | huart3.Init.BaudRate = 115200; 364 | huart3.Init.WordLength = UART_WORDLENGTH_8B; 365 | huart3.Init.StopBits = UART_STOPBITS_1; 366 | huart3.Init.Parity = UART_PARITY_NONE; 367 | huart3.Init.Mode = UART_MODE_TX_RX; 368 | huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; 369 | huart3.Init.OverSampling = UART_OVERSAMPLING_16; 370 | if (HAL_UART_Init(&huart3) != HAL_OK) 371 | { 372 | Error_Handler(); 373 | } 374 | /* USER CODE BEGIN USART3_Init 2 */ 375 | 376 | /* USER CODE END USART3_Init 2 */ 377 | 378 | } 379 | 380 | /** 381 | * @brief GPIO Initialization Function 382 | * @param None 383 | * @retval None 384 | */ 385 | static void MX_GPIO_Init(void) 386 | { 387 | GPIO_InitTypeDef GPIO_InitStruct = {0}; 388 | 389 | /* GPIO Ports Clock Enable */ 390 | __HAL_RCC_GPIOC_CLK_ENABLE(); 391 | __HAL_RCC_GPIOH_CLK_ENABLE(); 392 | __HAL_RCC_GPIOA_CLK_ENABLE(); 393 | __HAL_RCC_GPIOB_CLK_ENABLE(); 394 | 395 | /*Configure GPIO pin Output Level */ 396 | HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET); 397 | 398 | /*Configure GPIO pin : B1_Pin */ 399 | GPIO_InitStruct.Pin = B1_Pin; 400 | GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; 401 | GPIO_InitStruct.Pull = GPIO_NOPULL; 402 | HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct); 403 | 404 | /*Configure GPIO pin : LD2_Pin */ 405 | GPIO_InitStruct.Pin = LD2_Pin; 406 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 407 | GPIO_InitStruct.Pull = GPIO_NOPULL; 408 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; 409 | HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct); 410 | 411 | } 412 | 413 | /* USER CODE BEGIN 4 */ 414 | 415 | /* 416 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 417 | 418 | The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 419 | a byte string of which the beginning has been passed to the function. The 420 | number of bytes to check is also a parameter. 421 | 422 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 423 | */ 424 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes) { 425 | 426 | uint16_t crc; 427 | const unsigned char *ptr; 428 | size_t a; 429 | 430 | if (!crc_tab16_init) init_crc16_tab(); 431 | 432 | crc = CRC_START_MODBUS; 433 | ptr = input_str; 434 | 435 | if (ptr != NULL) for (a = 0; a < num_bytes; a++) { 436 | 437 | crc = (crc >> 8) ^ crc_tab16[(crc ^ (uint16_t)*ptr++) & 0x00FF]; 438 | } 439 | 440 | return crc; 441 | 442 | } /* crc_modbus */ 443 | 444 | /* 445 | static void init_crc16_tab( void ); 446 | 447 | For optimal performance uses the CRC16 routine a lookup table with values 448 | that can be used directly in the XOR arithmetic in the algorithm. This 449 | lookup table is calculated by the init_crc16_tab() routine, the first time 450 | the CRC function is called. 451 | 452 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 453 | */ 454 | static void init_crc16_tab(void) { 455 | 456 | uint16_t i; 457 | uint16_t j; 458 | uint16_t crc; 459 | uint16_t c; 460 | 461 | for (i = 0; i < 256; i++) { 462 | 463 | crc = 0; 464 | c = i; 465 | 466 | for (j = 0; j < 8; j++) { 467 | 468 | if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16; 469 | else crc = crc >> 1; 470 | 471 | c = c >> 1; 472 | } 473 | 474 | crc_tab16[i] = crc; 475 | } 476 | 477 | crc_tab16_init = true; 478 | 479 | } /* init_crc16_tab */ 480 | 481 | // map(value, fromLow, fromHigh, toLow, toHigh) 482 | long map(long x, long in_min, long in_max, long out_min, long out_max) { 483 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 484 | } 485 | 486 | /* USER CODE END 4 */ 487 | 488 | /** 489 | * @brief This function is executed in case of error occurrence. 490 | * @retval None 491 | */ 492 | void Error_Handler(void) 493 | { 494 | /* USER CODE BEGIN Error_Handler_Debug */ 495 | /* User can add his own implementation to report the HAL error return state */ 496 | __disable_irq(); 497 | while (1) 498 | { 499 | } 500 | /* USER CODE END Error_Handler_Debug */ 501 | } 502 | 503 | #ifdef USE_FULL_ASSERT 504 | /** 505 | * @brief Reports the name of the source file and the source line number 506 | * where the assert_param error has occurred. 507 | * @param file: pointer to the source file name 508 | * @param line: assert_param error line source number 509 | * @retval None 510 | */ 511 | void assert_failed(uint8_t *file, uint32_t line) 512 | { 513 | /* USER CODE BEGIN 6 */ 514 | /* User can add his own implementation to report the file name and line number, 515 | ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 516 | /* USER CODE END 6 */ 517 | } 518 | #endif /* USE_FULL_ASSERT */ 519 | -------------------------------------------------------------------------------- /code/Main_board-STM32_NUCLEO-F446RE/Main_Modbus_HMI_F446RE_v02.c: -------------------------------------------------------------------------------- 1 | /* USER CODE BEGIN Header */ 2 | 3 | /* 4 | Name: Main_Modbus_HMI_F446RE_v02.c 5 | Suggested board: STM32 NUCLEO-F446RE 6 | Suggested development environment: STM32CubeIDE 7 | 8 | Purpose: 9 | 1. Send Modbus-RTU command to the device through RS-485 via the UART1. 10 | 2. Get responses from the device through RS-485. 11 | 3. Send the device responses to the human-machine interface (HMI) via the UART3. 12 | 4. Users can monitor the device responses from the STM32CubeIDE console monitor via UART2. 13 | 14 | Suggested hardware setup: 15 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 16 | 2. UART3: A HMI with the UART interface is used to show the device responses through the UART3. 17 | Suggested software (STM32CubeIDE) setup: 18 | 1. Pinout & configuration/Connectivity 19 | 1.1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 20 | 1.2. Turn on USART2, Mode: Asynchronous, Basic parameters: 115200 8N1 21 | 1.3. Turn on USART3, Mode: Asynchronous, Basic parameters: 115200 8N1 22 | Suggested library for calculating CRC: 23 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 24 | 2. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 25 | 3. The key files of the library used in this project is put in the [library](library). 26 | 27 | Date: 17 Sep. 2021 28 | Author: Dr. Hsien-Ching Chung 29 | ORCID: https://orcid.org/0000-0001-9364-8858 30 | 31 | Project Link: https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32 32 | 33 | License: MIT License 34 | Copyright (c) 2021 Hsien-Ching Chung 35 | ------------------------------------ 36 | Update description: 37 | v02 2021-09-17 38 | 1. Show Modbus data in string format through UART2. 39 | 2. Send the message "Data receiving SUCCESS." through UART2 after receiving the Modbus data. 40 | 3. Correct some tyops in the comments. 41 | v01 2021-09-14 42 | Initial version. 43 | */ 44 | 45 | /* USER CODE END Header */ 46 | /* Includes ------------------------------------------------------------------*/ 47 | #include "main.h" 48 | 49 | /* Private includes ----------------------------------------------------------*/ 50 | /* USER CODE BEGIN Includes */ 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include "checksum.h" 56 | // "checksum.h" is the header file for using the crc_modbus() function. 57 | // Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/include/checksum.h 58 | /* USER CODE END Includes */ 59 | 60 | /* Private typedef -----------------------------------------------------------*/ 61 | /* USER CODE BEGIN PTD */ 62 | 63 | /* USER CODE END PTD */ 64 | 65 | /* Private define ------------------------------------------------------------*/ 66 | /* USER CODE BEGIN PD */ 67 | /* USER CODE END PD */ 68 | 69 | /* Private macro -------------------------------------------------------------*/ 70 | /* USER CODE BEGIN PM */ 71 | 72 | /* USER CODE END PM */ 73 | 74 | /* Private variables ---------------------------------------------------------*/ 75 | UART_HandleTypeDef huart1; 76 | UART_HandleTypeDef huart2; 77 | UART_HandleTypeDef huart3; 78 | 79 | /* USER CODE BEGIN PV */ 80 | 81 | /* USER CODE END PV */ 82 | 83 | /* Private function prototypes -----------------------------------------------*/ 84 | void SystemClock_Config(void); 85 | static void MX_GPIO_Init(void); 86 | static void MX_USART2_UART_Init(void); 87 | static void MX_USART1_UART_Init(void); 88 | static void MX_USART3_UART_Init(void); 89 | /* USER CODE BEGIN PFP */ 90 | 91 | /* USER CODE END PFP */ 92 | 93 | /* Private user code ---------------------------------------------------------*/ 94 | /* USER CODE BEGIN 0 */ 95 | // CRC16-related functions 96 | static void init_crc16_tab(void); 97 | static bool crc_tab16_init = false; 98 | static uint16_t crc_tab16[256]; 99 | long map(); 100 | /* USER CODE END 0 */ 101 | 102 | /** 103 | * @brief The application entry point. 104 | * @retval int 105 | */ 106 | int main(void) 107 | { 108 | /* USER CODE BEGIN 1 */ 109 | uint8_t buf[64]; // Buffer for message. 110 | 111 | // Modbus-RTU Command 112 | uint8_t Command_Modbus[8] = {0x01,0x04,0x30,0x00,0x00,0x0B,0xBE,0xCD}; 113 | // [Modbus address convention] 114 | // The usage of the Modbus address (0x3000 = 12288) in "Command_Modbus[8]" does not follow the traditional Modbus address convention. 115 | // Convention: discrete input numbers (1 bit (off/on), 0 or 1) start with 1 and span from 10001 to 19999. 116 | // The users are suggested to design the system following the address convention (or following the device's address convention). 117 | // Ref: https://en.wikipedia.org/wiki/Modbus 118 | 119 | uint8_t Data_Modbus[27]; // Modbus-RTU Data 120 | 121 | // Device responses to Command_Modbus 122 | uint16_t VAC_out; 123 | float VAC_out_f; 124 | uint16_t V_Batt; 125 | float V_Batt_f; 126 | uint16_t Load_percent; 127 | uint16_t Load_power; 128 | 129 | uint8_t EndHex[3] = {0xFF,0xFF,0xFF}; // End command for HMI 130 | /* USER CODE END 1 */ 131 | 132 | /* MCU Configuration--------------------------------------------------------*/ 133 | 134 | /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 135 | HAL_Init(); 136 | 137 | /* USER CODE BEGIN Init */ 138 | 139 | /* USER CODE END Init */ 140 | 141 | /* Configure the system clock */ 142 | SystemClock_Config(); 143 | 144 | /* USER CODE BEGIN SysInit */ 145 | 146 | /* USER CODE END SysInit */ 147 | 148 | /* Initialize all configured peripherals */ 149 | MX_GPIO_Init(); 150 | MX_USART2_UART_Init(); 151 | MX_USART1_UART_Init(); 152 | MX_USART3_UART_Init(); 153 | /* USER CODE BEGIN 2 */ 154 | 155 | /* USER CODE END 2 */ 156 | 157 | /* Infinite loop */ 158 | /* USER CODE BEGIN WHILE */ 159 | while (1) 160 | { 161 | HAL_UART_Transmit(&huart1, Command_Modbus, sizeof(Command_Modbus), 100); // Send Modbus command through UART1 162 | if( HAL_UART_Receive(&huart1, Data_Modbus, sizeof(Data_Modbus), 2000) == HAL_OK ) { // When receiving data from UART1 successfully 163 | 164 | // Send message to STM32CubeIDE console monitor through UART2 165 | strcpy((char*)buf, "Data receiving SUCCESS.\r\n"); 166 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 167 | 168 | //Show the received Modbus data on the serial monitor through UART2 169 | strcpy((char*)buf, "Received Modbus data:\r\n"); 170 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 171 | 172 | for (int i = 0; i < sizeof(Data_Modbus); i++) { 173 | // Show the value on the serial monitor 174 | sprintf((char*)buf, " %02X", Data_Modbus[i]); 175 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 176 | } 177 | 178 | strcpy((char*)buf, "\r\n"); 179 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 180 | 181 | 182 | // Get the CRC16 of the received Modbus data 183 | uint16_t CRC16_data = ( Data_Modbus[sizeof(Data_Modbus)-1] << 8 ) | Data_Modbus[sizeof(Data_Modbus)-2]; 184 | 185 | // Calculate the CRC16 of the received Modbus data by the function 186 | // crc_modbus(const unsigned char *input_str, size_t num_bytes) 187 | uint16_t CRC16_cal = crc_modbus( Data_Modbus, sizeof(Data_Modbus)-2 ); 188 | 189 | 190 | if ( CRC16_data == CRC16_cal ) { // When CRC16 check is passed successfully 191 | 192 | // Send message to STM32CubeIDE console monitor through UART2 193 | strcpy((char*)buf, "CRC16 check PASSED.\r\n"); 194 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 195 | 196 | uint16_t value = (Data_Modbus[5] << 8) | Data_Modbus[6]; // Combine/Merge two bytes into one 197 | VAC_out = value; 198 | VAC_out_f = (float)value / 10.0; // Resolve the value 199 | 200 | sprintf((char*)buf, "VAC_out(V): %f\r\n", VAC_out_f); // Show the value on the serial monitor 201 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 202 | 203 | sprintf((char*)buf, "x1.val=%d", VAC_out); // Send device response to HMI 204 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 205 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 206 | 207 | 208 | value = (Data_Modbus[7] << 8) | Data_Modbus[8]; // Combine/Merge two bytes into one 209 | V_Batt = value; 210 | V_Batt_f = (float)value / 10.0; // Resolve the value 211 | 212 | sprintf((char*)buf, "V_Batt(V): %f\r\n", V_Batt_f); // Show the value on the serial monitor 213 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 214 | 215 | sprintf((char*)buf, "x0.val=%d", V_Batt); // Send device response to HMI 216 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 217 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 218 | 219 | 220 | Load_percent = Data_Modbus[12]; // Resolve the value 221 | 222 | sprintf((char*)buf, "Load_percent(%%): %d\r\n", Load_percent); // Show the value on the serial monitor 223 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 224 | 225 | sprintf((char*)buf, "n0.val=%d", Load_percent); // Send device response to HMI 226 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 227 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 228 | 229 | 230 | value = (Data_Modbus[13] << 8) | Data_Modbus[14]; // Combine/Merge two bytes into one 231 | Load_power = value; // Resolve the value 232 | 233 | sprintf((char*)buf, "Load_power(W): %d\r\n", Load_power); // Show the value on the serial monitor 234 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 235 | 236 | int HMI_Pointer = map(Load_power, 0, 2000, 0, 240); // map(value, fromLow, fromHigh, toLow, toHigh) 237 | 238 | sprintf((char*)buf, "z0.val=%d", HMI_Pointer); // Send device response to HMI 239 | HAL_UART_Transmit(&huart3, buf, strlen((char*)buf), 100); 240 | HAL_UART_Transmit(&huart3, EndHex, sizeof(EndHex), 100); 241 | 242 | } else { 243 | // Send message to STM32CubeIDE console monitor through UART2 244 | strcpy((char*)buf, "CRC16 check FAILED.\r\n"); 245 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 246 | } 247 | } else { 248 | // Send message to STM32CubeIDE console monitor through UART2 249 | strcpy((char*)buf, "Data receive FAILED. Resend Modbus command.\r\n"); 250 | HAL_UART_Transmit(&huart2, buf, strlen((char*)buf), 100); 251 | } 252 | 253 | //HAL_Delay(50); // Data processing rate control 254 | 255 | /* USER CODE END WHILE */ 256 | 257 | /* USER CODE BEGIN 3 */ 258 | } 259 | /* USER CODE END 3 */ 260 | } 261 | 262 | /** 263 | * @brief System Clock Configuration 264 | * @retval None 265 | */ 266 | void SystemClock_Config(void) 267 | { 268 | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 269 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 270 | 271 | /** Configure the main internal regulator output voltage 272 | */ 273 | __HAL_RCC_PWR_CLK_ENABLE(); 274 | __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3); 275 | /** Initializes the RCC Oscillators according to the specified parameters 276 | * in the RCC_OscInitTypeDef structure. 277 | */ 278 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 279 | RCC_OscInitStruct.HSIState = RCC_HSI_ON; 280 | RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 281 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 282 | RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; 283 | RCC_OscInitStruct.PLL.PLLM = 16; 284 | RCC_OscInitStruct.PLL.PLLN = 336; 285 | RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4; 286 | RCC_OscInitStruct.PLL.PLLQ = 2; 287 | RCC_OscInitStruct.PLL.PLLR = 2; 288 | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 289 | { 290 | Error_Handler(); 291 | } 292 | /** Initializes the CPU, AHB and APB buses clocks 293 | */ 294 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 295 | |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 296 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 297 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 298 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; 299 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 300 | 301 | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) 302 | { 303 | Error_Handler(); 304 | } 305 | } 306 | 307 | /** 308 | * @brief USART1 Initialization Function 309 | * @param None 310 | * @retval None 311 | */ 312 | static void MX_USART1_UART_Init(void) 313 | { 314 | 315 | /* USER CODE BEGIN USART1_Init 0 */ 316 | 317 | /* USER CODE END USART1_Init 0 */ 318 | 319 | /* USER CODE BEGIN USART1_Init 1 */ 320 | 321 | /* USER CODE END USART1_Init 1 */ 322 | huart1.Instance = USART1; 323 | huart1.Init.BaudRate = 9600; 324 | huart1.Init.WordLength = UART_WORDLENGTH_8B; 325 | huart1.Init.StopBits = UART_STOPBITS_1; 326 | huart1.Init.Parity = UART_PARITY_NONE; 327 | huart1.Init.Mode = UART_MODE_TX_RX; 328 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 329 | huart1.Init.OverSampling = UART_OVERSAMPLING_16; 330 | if (HAL_UART_Init(&huart1) != HAL_OK) 331 | { 332 | Error_Handler(); 333 | } 334 | /* USER CODE BEGIN USART1_Init 2 */ 335 | 336 | /* USER CODE END USART1_Init 2 */ 337 | 338 | } 339 | 340 | /** 341 | * @brief USART2 Initialization Function 342 | * @param None 343 | * @retval None 344 | */ 345 | static void MX_USART2_UART_Init(void) 346 | { 347 | 348 | /* USER CODE BEGIN USART2_Init 0 */ 349 | 350 | /* USER CODE END USART2_Init 0 */ 351 | 352 | /* USER CODE BEGIN USART2_Init 1 */ 353 | 354 | /* USER CODE END USART2_Init 1 */ 355 | huart2.Instance = USART2; 356 | huart2.Init.BaudRate = 115200; 357 | huart2.Init.WordLength = UART_WORDLENGTH_8B; 358 | huart2.Init.StopBits = UART_STOPBITS_1; 359 | huart2.Init.Parity = UART_PARITY_NONE; 360 | huart2.Init.Mode = UART_MODE_TX_RX; 361 | huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; 362 | huart2.Init.OverSampling = UART_OVERSAMPLING_16; 363 | if (HAL_UART_Init(&huart2) != HAL_OK) 364 | { 365 | Error_Handler(); 366 | } 367 | /* USER CODE BEGIN USART2_Init 2 */ 368 | 369 | /* USER CODE END USART2_Init 2 */ 370 | 371 | } 372 | 373 | /** 374 | * @brief USART3 Initialization Function 375 | * @param None 376 | * @retval None 377 | */ 378 | static void MX_USART3_UART_Init(void) 379 | { 380 | 381 | /* USER CODE BEGIN USART3_Init 0 */ 382 | 383 | /* USER CODE END USART3_Init 0 */ 384 | 385 | /* USER CODE BEGIN USART3_Init 1 */ 386 | 387 | /* USER CODE END USART3_Init 1 */ 388 | huart3.Instance = USART3; 389 | huart3.Init.BaudRate = 115200; 390 | huart3.Init.WordLength = UART_WORDLENGTH_8B; 391 | huart3.Init.StopBits = UART_STOPBITS_1; 392 | huart3.Init.Parity = UART_PARITY_NONE; 393 | huart3.Init.Mode = UART_MODE_TX_RX; 394 | huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; 395 | huart3.Init.OverSampling = UART_OVERSAMPLING_16; 396 | if (HAL_UART_Init(&huart3) != HAL_OK) 397 | { 398 | Error_Handler(); 399 | } 400 | /* USER CODE BEGIN USART3_Init 2 */ 401 | 402 | /* USER CODE END USART3_Init 2 */ 403 | 404 | } 405 | 406 | /** 407 | * @brief GPIO Initialization Function 408 | * @param None 409 | * @retval None 410 | */ 411 | static void MX_GPIO_Init(void) 412 | { 413 | GPIO_InitTypeDef GPIO_InitStruct = {0}; 414 | 415 | /* GPIO Ports Clock Enable */ 416 | __HAL_RCC_GPIOC_CLK_ENABLE(); 417 | __HAL_RCC_GPIOH_CLK_ENABLE(); 418 | __HAL_RCC_GPIOA_CLK_ENABLE(); 419 | __HAL_RCC_GPIOB_CLK_ENABLE(); 420 | 421 | /*Configure GPIO pin Output Level */ 422 | HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET); 423 | 424 | /*Configure GPIO pin : B1_Pin */ 425 | GPIO_InitStruct.Pin = B1_Pin; 426 | GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; 427 | GPIO_InitStruct.Pull = GPIO_NOPULL; 428 | HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct); 429 | 430 | /*Configure GPIO pin : LD2_Pin */ 431 | GPIO_InitStruct.Pin = LD2_Pin; 432 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 433 | GPIO_InitStruct.Pull = GPIO_NOPULL; 434 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; 435 | HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct); 436 | 437 | } 438 | 439 | /* USER CODE BEGIN 4 */ 440 | 441 | /* 442 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 443 | 444 | The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 445 | a byte string of which the beginning has been passed to the function. The 446 | number of bytes to check is also a parameter. 447 | 448 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 449 | */ 450 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes) { 451 | 452 | uint16_t crc; 453 | const unsigned char *ptr; 454 | size_t a; 455 | 456 | if (!crc_tab16_init) init_crc16_tab(); 457 | 458 | crc = CRC_START_MODBUS; 459 | ptr = input_str; 460 | 461 | if (ptr != NULL) for (a = 0; a < num_bytes; a++) { 462 | 463 | crc = (crc >> 8) ^ crc_tab16[(crc ^ (uint16_t)*ptr++) & 0x00FF]; 464 | } 465 | 466 | return crc; 467 | 468 | } /* crc_modbus */ 469 | 470 | /* 471 | static void init_crc16_tab( void ); 472 | 473 | For optimal performance uses the CRC16 routine a lookup table with values 474 | that can be used directly in the XOR arithmetic in the algorithm. This 475 | lookup table is calculated by the init_crc16_tab() routine, the first time 476 | the CRC function is called. 477 | 478 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 479 | */ 480 | static void init_crc16_tab(void) { 481 | 482 | uint16_t i; 483 | uint16_t j; 484 | uint16_t crc; 485 | uint16_t c; 486 | 487 | for (i = 0; i < 256; i++) { 488 | 489 | crc = 0; 490 | c = i; 491 | 492 | for (j = 0; j < 8; j++) { 493 | 494 | if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16; 495 | else crc = crc >> 1; 496 | 497 | c = c >> 1; 498 | } 499 | 500 | crc_tab16[i] = crc; 501 | } 502 | 503 | crc_tab16_init = true; 504 | 505 | } /* init_crc16_tab */ 506 | 507 | // map(value, fromLow, fromHigh, toLow, toHigh) 508 | long map(long x, long in_min, long in_max, long out_min, long out_max) { 509 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 510 | } 511 | 512 | /* USER CODE END 4 */ 513 | 514 | /** 515 | * @brief This function is executed in case of error occurrence. 516 | * @retval None 517 | */ 518 | void Error_Handler(void) 519 | { 520 | /* USER CODE BEGIN Error_Handler_Debug */ 521 | /* User can add his own implementation to report the HAL error return state */ 522 | __disable_irq(); 523 | while (1) 524 | { 525 | } 526 | /* USER CODE END Error_Handler_Debug */ 527 | } 528 | 529 | #ifdef USE_FULL_ASSERT 530 | /** 531 | * @brief Reports the name of the source file and the source line number 532 | * where the assert_param error has occurred. 533 | * @param file: pointer to the source file name 534 | * @param line: assert_param error line source number 535 | * @retval None 536 | */ 537 | void assert_failed(uint8_t *file, uint32_t line) 538 | { 539 | /* USER CODE BEGIN 6 */ 540 | /* User can add his own implementation to report the file name and line number, 541 | ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 542 | /* USER CODE END 6 */ 543 | } 544 | #endif /* USE_FULL_ASSERT */ 545 | -------------------------------------------------------------------------------- /code/Test_board-Blue_Pill/Test_Modbus_HMI_BluePill_v01.c: -------------------------------------------------------------------------------- 1 | /* USER CODE BEGIN Header */ 2 | 3 | /* 4 | Name: Test_Modbus_HMI_BluePill_v01.c 5 | Suggested board: Blue Pill (STM32F103C8T6) 6 | Suggested development environment: STM32CubeIDE 7 | 8 | Purpose: 9 | 1. Receive Modbus-RTU command from the main board through RS-485 via the UART1. 10 | 2. Generate the Modbus data with CRC and counting value. 11 | 3. Send the Modbus data back. 12 | 13 | Suggested hardware setup: 14 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 15 | Suggested software (STM32CubeIDE) setup: 16 | 1. Pinout & configuration/Connectivity 17 | 1.1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 18 | Suggested library for calculating CRC: 19 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 20 | 2. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 21 | 3. The key files of the library used in this project is put in the [library](library). 22 | 23 | Date: 14 Sep. 2021 24 | Author: Dr. Hsien-Ching Chung 25 | ORCID: https://orcid.org/0000-0001-9364-8858 26 | 27 | Project Link: https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32 28 | 29 | License: MIT License 30 | Copyright (c) 2021 Hsien-Ching Chung 31 | */ 32 | 33 | /* USER CODE END Header */ 34 | /* Includes ------------------------------------------------------------------*/ 35 | #include "main.h" 36 | 37 | /* Private includes ----------------------------------------------------------*/ 38 | /* USER CODE BEGIN Includes */ 39 | #include 40 | #include 41 | #include "checksum.h" 42 | // "checksum.h" is the header file for using the crc_modbus() function. 43 | // This file should be placed in the "[Project]/Core/Inc" folder. 44 | // Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/include/checksum.h 45 | /* USER CODE END Includes */ 46 | 47 | /* Private typedef -----------------------------------------------------------*/ 48 | /* USER CODE BEGIN PTD */ 49 | 50 | /* USER CODE END PTD */ 51 | 52 | /* Private define ------------------------------------------------------------*/ 53 | /* USER CODE BEGIN PD */ 54 | /* USER CODE END PD */ 55 | 56 | /* Private macro -------------------------------------------------------------*/ 57 | /* USER CODE BEGIN PM */ 58 | 59 | /* USER CODE END PM */ 60 | 61 | /* Private variables ---------------------------------------------------------*/ 62 | UART_HandleTypeDef huart1; 63 | 64 | /* USER CODE BEGIN PV */ 65 | 66 | /* USER CODE END PV */ 67 | 68 | /* Private function prototypes -----------------------------------------------*/ 69 | void SystemClock_Config(void); 70 | static void MX_GPIO_Init(void); 71 | static void MX_USART1_UART_Init(void); 72 | /* USER CODE BEGIN PFP */ 73 | 74 | /* USER CODE END PFP */ 75 | 76 | /* Private user code ---------------------------------------------------------*/ 77 | /* USER CODE BEGIN 0 */ 78 | // CRC16-related functions 79 | static void init_crc16_tab(void); 80 | static bool crc_tab16_init = false; 81 | static uint16_t crc_tab16[256]; 82 | /* USER CODE END 0 */ 83 | 84 | /** 85 | * @brief The application entry point. 86 | * @retval int 87 | */ 88 | int main(void) 89 | { 90 | /* USER CODE BEGIN 1 */ 91 | uint8_t buf[8]; // Buffer for message. 92 | 93 | // Modbus-RTU Command 94 | uint8_t Command_Modbus[8] = {0x01,0x04,0x30,0x00,0x00,0x0B,0xBE,0xCD}; 95 | // [Modbus address convention] 96 | // The usage of the Modbus address (0x3000 = 12288) in "Command_Modbus[8]" does not follow the traditional Modbus address convention. 97 | // Convention: discrete input numbers (1 bit (off/on), 0 or 1) start with 1 and span from 10001 to 19999. 98 | // The users are suggested to design the system following the address convention (or following the device's address convention). 99 | // Ref: https://en.wikipedia.org/wiki/Modbus 100 | 101 | // Modbus-RTU Data 102 | uint8_t Data_Modbus[27] = { 0x01,0x04,0x16,0x00,0x14,0x04,0xB0,0x02,0x00,0x00, 103 | 0x15,0x00,0x0C,0x00,0xFF,0x00,0x0E,0x00,0x27,0x00, 104 | 0x00,0x00,0x22,0x00,0x02,0x9A,0xC7}; 105 | 106 | uint16_t value = 0; // Value generator 107 | /* USER CODE END 1 */ 108 | 109 | /* MCU Configuration--------------------------------------------------------*/ 110 | 111 | /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 112 | HAL_Init(); 113 | 114 | /* USER CODE BEGIN Init */ 115 | 116 | /* USER CODE END Init */ 117 | 118 | /* Configure the system clock */ 119 | SystemClock_Config(); 120 | 121 | /* USER CODE BEGIN SysInit */ 122 | 123 | /* USER CODE END SysInit */ 124 | 125 | /* Initialize all configured peripherals */ 126 | MX_GPIO_Init(); 127 | MX_USART1_UART_Init(); 128 | /* USER CODE BEGIN 2 */ 129 | 130 | /* USER CODE END 2 */ 131 | 132 | /* Infinite loop */ 133 | /* USER CODE BEGIN WHILE */ 134 | while (1) 135 | { 136 | if( HAL_UART_Receive(&huart1, buf, sizeof(buf), 2000) == HAL_OK ) { // When receiving data from UART1 successfully 137 | if( buf[6] == Command_Modbus[6] && buf[7] == Command_Modbus[7] ) { // When CRC16 check is passed successfully 138 | 139 | // Turn on the On-board LED to show the beginning of the data processing. 140 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); 141 | 142 | value += 1; // Value generator 143 | 144 | Data_Modbus[5] = value >> 8; // Put the High byte of "value" in the array element. 145 | Data_Modbus[6] = value; // Put the Low byte of "value" in the array element. 146 | 147 | // Use crc_modbus(const unsigned char *input_str, size_t num_bytes) to calculate CRC16 148 | uint16_t CRC16_cal = crc_modbus( Data_Modbus, sizeof(Data_Modbus)-2 ); 149 | 150 | Data_Modbus[25] = CRC16_cal; // Put the Low byte of "CRC16_cal" in the array element. 151 | Data_Modbus[26] = CRC16_cal >> 8; // Put the High byte of "CRC16_cal" in the array element. 152 | 153 | HAL_UART_Transmit(&huart1, Data_Modbus, sizeof(Data_Modbus), 100); // Send the Modbus data out through UART1. 154 | 155 | // Turn off the On-board LED to show the beginning of the data processing. 156 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); 157 | } 158 | } 159 | /* USER CODE END WHILE */ 160 | 161 | /* USER CODE BEGIN 3 */ 162 | } 163 | /* USER CODE END 3 */ 164 | } 165 | 166 | /** 167 | * @brief System Clock Configuration 168 | * @retval None 169 | */ 170 | void SystemClock_Config(void) 171 | { 172 | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 173 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 174 | 175 | /** Initializes the RCC Oscillators according to the specified parameters 176 | * in the RCC_OscInitTypeDef structure. 177 | */ 178 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 179 | RCC_OscInitStruct.HSIState = RCC_HSI_ON; 180 | RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 181 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; 182 | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 183 | { 184 | Error_Handler(); 185 | } 186 | /** Initializes the CPU, AHB and APB buses clocks 187 | */ 188 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 189 | |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 190 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; 191 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 192 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; 193 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 194 | 195 | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) 196 | { 197 | Error_Handler(); 198 | } 199 | } 200 | 201 | /** 202 | * @brief USART1 Initialization Function 203 | * @param None 204 | * @retval None 205 | */ 206 | static void MX_USART1_UART_Init(void) 207 | { 208 | 209 | /* USER CODE BEGIN USART1_Init 0 */ 210 | 211 | /* USER CODE END USART1_Init 0 */ 212 | 213 | /* USER CODE BEGIN USART1_Init 1 */ 214 | 215 | /* USER CODE END USART1_Init 1 */ 216 | huart1.Instance = USART1; 217 | huart1.Init.BaudRate = 9600; 218 | huart1.Init.WordLength = UART_WORDLENGTH_8B; 219 | huart1.Init.StopBits = UART_STOPBITS_1; 220 | huart1.Init.Parity = UART_PARITY_NONE; 221 | huart1.Init.Mode = UART_MODE_TX_RX; 222 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 223 | huart1.Init.OverSampling = UART_OVERSAMPLING_16; 224 | if (HAL_UART_Init(&huart1) != HAL_OK) 225 | { 226 | Error_Handler(); 227 | } 228 | /* USER CODE BEGIN USART1_Init 2 */ 229 | 230 | /* USER CODE END USART1_Init 2 */ 231 | 232 | } 233 | 234 | /** 235 | * @brief GPIO Initialization Function 236 | * @param None 237 | * @retval None 238 | */ 239 | static void MX_GPIO_Init(void) 240 | { 241 | GPIO_InitTypeDef GPIO_InitStruct = {0}; 242 | 243 | /* GPIO Ports Clock Enable */ 244 | __HAL_RCC_GPIOC_CLK_ENABLE(); 245 | __HAL_RCC_GPIOA_CLK_ENABLE(); 246 | 247 | /*Configure GPIO pin Output Level */ 248 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); 249 | 250 | /*Configure GPIO pin : PC13 */ 251 | GPIO_InitStruct.Pin = GPIO_PIN_13; 252 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 253 | GPIO_InitStruct.Pull = GPIO_NOPULL; 254 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; 255 | HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); 256 | 257 | } 258 | 259 | /* USER CODE BEGIN 4 */ 260 | 261 | /* 262 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 263 | The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 264 | a byte string of which the beginning has been passed to the function. The 265 | number of bytes to check is also a parameter. 266 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 267 | */ 268 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes) { 269 | 270 | uint16_t crc; 271 | const unsigned char *ptr; 272 | size_t a; 273 | 274 | if (!crc_tab16_init) init_crc16_tab(); 275 | 276 | crc = CRC_START_MODBUS; 277 | ptr = input_str; 278 | 279 | if (ptr != NULL) for (a = 0; a < num_bytes; a++) { 280 | 281 | crc = (crc >> 8) ^ crc_tab16[(crc ^ (uint16_t)*ptr++) & 0x00FF]; 282 | } 283 | 284 | return crc; 285 | 286 | } /* crc_modbus */ 287 | 288 | /* 289 | static void init_crc16_tab( void ); 290 | For optimal performance uses the CRC16 routine a lookup table with values 291 | that can be used directly in the XOR arithmetic in the algorithm. This 292 | lookup table is calculated by the init_crc16_tab() routine, the first time 293 | the CRC function is called. 294 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 295 | */ 296 | static void init_crc16_tab(void) { 297 | 298 | uint16_t i; 299 | uint16_t j; 300 | uint16_t crc; 301 | uint16_t c; 302 | 303 | for (i = 0; i < 256; i++) { 304 | 305 | crc = 0; 306 | c = i; 307 | 308 | for (j = 0; j < 8; j++) { 309 | 310 | if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16; 311 | else crc = crc >> 1; 312 | 313 | c = c >> 1; 314 | } 315 | 316 | crc_tab16[i] = crc; 317 | } 318 | 319 | crc_tab16_init = true; 320 | 321 | } /* init_crc16_tab */ 322 | 323 | /* USER CODE END 4 */ 324 | 325 | /** 326 | * @brief This function is executed in case of error occurrence. 327 | * @retval None 328 | */ 329 | void Error_Handler(void) 330 | { 331 | /* USER CODE BEGIN Error_Handler_Debug */ 332 | /* User can add his own implementation to report the HAL error return state */ 333 | __disable_irq(); 334 | while (1) 335 | { 336 | } 337 | /* USER CODE END Error_Handler_Debug */ 338 | } 339 | 340 | #ifdef USE_FULL_ASSERT 341 | /** 342 | * @brief Reports the name of the source file and the source line number 343 | * where the assert_param error has occurred. 344 | * @param file: pointer to the source file name 345 | * @param line: assert_param error line source number 346 | * @retval None 347 | */ 348 | void assert_failed(uint8_t *file, uint32_t line) 349 | { 350 | /* USER CODE BEGIN 6 */ 351 | /* User can add his own implementation to report the file name and line number, 352 | ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 353 | /* USER CODE END 6 */ 354 | } 355 | #endif /* USE_FULL_ASSERT */ 356 | -------------------------------------------------------------------------------- /code/Test_board-STM32_NUCLEO-F446RE/Test_Modbus_HMI_F446RE_v01.c: -------------------------------------------------------------------------------- 1 | /* USER CODE BEGIN Header */ 2 | 3 | /* 4 | Name: Test_Modbus_HMI_F446RE_v01.c 5 | Suggested board: STM32 NUCLEO-F446RE 6 | Suggested development environment: STM32CubeIDE 7 | 8 | Purpose: 9 | 1. Receive Modbus-RTU command from the main board through RS-485 via the UART1. 10 | 2. Generate the Modbus data with CRC and counting value. 11 | 3. Send the Modbus data back. 12 | 13 | Suggested hardware setup: 14 | 1. UART1: A "RS-485 to TTL module" is used to convert the RS-485 signal because STM32 NUCLEO-F446RE (and Blue Pill) does not support RS-485 directly. 15 | Suggested software (STM32CubeIDE) setup: 16 | 1. Pinout & configuration/Connectivity 17 | 1.1. Turn on USART1, Mode: Asynchronous, Basic parameters: 9600 8N1 18 | Suggested library for calculating CRC: 19 | 1. The CRC 16 calculation function is available from Lammert Bies, https://github.com/lammertb/libcrc . 20 | 2. On-line CRC calculation and free library, https://www.lammertbies.nl/comm/info/crc-calculation . 21 | 3. The key files of the library used in this project is put in the [library](library). 22 | 23 | Date: 15 Sep. 2021 24 | Author: Dr. Hsien-Ching Chung 25 | ORCID: https://orcid.org/0000-0001-9364-8858 26 | 27 | Project Link: https://github.com/HsienChing/RS-485_Modbus-RTU_Call_Response_and_HMI_Display_with_CRC_for_STM32 28 | 29 | License: MIT License 30 | Copyright (c) 2021 Hsien-Ching Chung 31 | */ 32 | 33 | /* USER CODE END Header */ 34 | /* Includes ------------------------------------------------------------------*/ 35 | #include "main.h" 36 | 37 | /* Private includes ----------------------------------------------------------*/ 38 | /* USER CODE BEGIN Includes */ 39 | #include 40 | #include 41 | #include "checksum.h" 42 | // "checksum.h" is the header file for using the crc_modbus() function. 43 | // This file should be placed in the "[Project]/Core/Inc" folder. 44 | // Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/include/checksum.h 45 | /* USER CODE END Includes */ 46 | 47 | /* Private typedef -----------------------------------------------------------*/ 48 | /* USER CODE BEGIN PTD */ 49 | 50 | /* USER CODE END PTD */ 51 | 52 | /* Private define ------------------------------------------------------------*/ 53 | /* USER CODE BEGIN PD */ 54 | /* USER CODE END PD */ 55 | 56 | /* Private macro -------------------------------------------------------------*/ 57 | /* USER CODE BEGIN PM */ 58 | 59 | /* USER CODE END PM */ 60 | 61 | /* Private variables ---------------------------------------------------------*/ 62 | UART_HandleTypeDef huart1; 63 | UART_HandleTypeDef huart2; 64 | 65 | /* USER CODE BEGIN PV */ 66 | 67 | /* USER CODE END PV */ 68 | 69 | /* Private function prototypes -----------------------------------------------*/ 70 | void SystemClock_Config(void); 71 | static void MX_GPIO_Init(void); 72 | static void MX_USART2_UART_Init(void); 73 | static void MX_USART1_UART_Init(void); 74 | /* USER CODE BEGIN PFP */ 75 | 76 | /* USER CODE END PFP */ 77 | 78 | /* Private user code ---------------------------------------------------------*/ 79 | /* USER CODE BEGIN 0 */ 80 | // CRC16-related functions 81 | static void init_crc16_tab(void); 82 | static bool crc_tab16_init = false; 83 | static uint16_t crc_tab16[256]; 84 | /* USER CODE END 0 */ 85 | 86 | /** 87 | * @brief The application entry point. 88 | * @retval int 89 | */ 90 | int main(void) 91 | { 92 | /* USER CODE BEGIN 1 */ 93 | uint8_t buf[8]; // Buffer for message. 94 | 95 | // Modbus-RTU Command 96 | uint8_t Command_Modbus[8] = {0x01,0x04,0x30,0x00,0x00,0x0B,0xBE,0xCD}; 97 | // [Modbus address convention] 98 | // The usage of the Modbus address (0x3000 = 12288) in "Command_Modbus[8]" does not follow the traditional Modbus address convention. 99 | // Convention: discrete input numbers (1 bit (off/on), 0 or 1) start with 1 and span from 10001 to 19999. 100 | // The users are suggested to design the system following the address convention (or following the device's address convention). 101 | // Ref: https://en.wikipedia.org/wiki/Modbus 102 | 103 | // Modbus-RTU Data 104 | uint8_t Data_Modbus[27] = { 0x01,0x04,0x16,0x00,0x14,0x04,0xB0,0x02,0x00,0x00, 105 | 0x15,0x00,0x0C,0x00,0xFF,0x00,0x0E,0x00,0x27,0x00, 106 | 0x00,0x00,0x22,0x00,0x02,0x9A,0xC7}; 107 | 108 | uint16_t value = 0; // Value generator 109 | /* USER CODE END 1 */ 110 | 111 | /* MCU Configuration--------------------------------------------------------*/ 112 | 113 | /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 114 | HAL_Init(); 115 | 116 | /* USER CODE BEGIN Init */ 117 | 118 | /* USER CODE END Init */ 119 | 120 | /* Configure the system clock */ 121 | SystemClock_Config(); 122 | 123 | /* USER CODE BEGIN SysInit */ 124 | 125 | /* USER CODE END SysInit */ 126 | 127 | /* Initialize all configured peripherals */ 128 | MX_GPIO_Init(); 129 | MX_USART2_UART_Init(); 130 | MX_USART1_UART_Init(); 131 | /* USER CODE BEGIN 2 */ 132 | 133 | /* USER CODE END 2 */ 134 | 135 | /* Infinite loop */ 136 | /* USER CODE BEGIN WHILE */ 137 | while (1) 138 | { 139 | if( HAL_UART_Receive(&huart1, buf, sizeof(buf), 2000) == HAL_OK ) { // When receiving data from UART1 successfully 140 | if( buf[6] == Command_Modbus[6] && buf[7] == Command_Modbus[7] ) { // When CRC16 check is passed successfully 141 | 142 | // Turn on the On-board LED to show the beginning of the data processing. 143 | HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); 144 | 145 | value += 1; // Value generator 146 | 147 | Data_Modbus[5] = value >> 8; // Put the High byte of "value" in the array element. 148 | Data_Modbus[6] = value; // Put the Low byte of "value" in the array element. 149 | 150 | // Use crc_modbus(const unsigned char *input_str, size_t num_bytes) to calculate CRC16 151 | uint16_t CRC16_cal = crc_modbus( Data_Modbus, sizeof(Data_Modbus)-2 ); 152 | 153 | Data_Modbus[25] = CRC16_cal; // Put the Low byte of "CRC16_cal" in the array element. 154 | Data_Modbus[26] = CRC16_cal >> 8; // Put the High byte of "CRC16_cal" in the array element. 155 | 156 | HAL_UART_Transmit(&huart1, Data_Modbus, sizeof(Data_Modbus), 100); // Send the Modbus data out through UART1. 157 | 158 | // Turn off the On-board LED to show the beginning of the data processing. 159 | HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); 160 | } 161 | } 162 | /* USER CODE END WHILE */ 163 | 164 | /* USER CODE BEGIN 3 */ 165 | } 166 | /* USER CODE END 3 */ 167 | } 168 | 169 | /** 170 | * @brief System Clock Configuration 171 | * @retval None 172 | */ 173 | void SystemClock_Config(void) 174 | { 175 | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 176 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 177 | 178 | /** Configure the main internal regulator output voltage 179 | */ 180 | __HAL_RCC_PWR_CLK_ENABLE(); 181 | __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3); 182 | /** Initializes the RCC Oscillators according to the specified parameters 183 | * in the RCC_OscInitTypeDef structure. 184 | */ 185 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 186 | RCC_OscInitStruct.HSIState = RCC_HSI_ON; 187 | RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 188 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 189 | RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; 190 | RCC_OscInitStruct.PLL.PLLM = 16; 191 | RCC_OscInitStruct.PLL.PLLN = 336; 192 | RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4; 193 | RCC_OscInitStruct.PLL.PLLQ = 2; 194 | RCC_OscInitStruct.PLL.PLLR = 2; 195 | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 196 | { 197 | Error_Handler(); 198 | } 199 | /** Initializes the CPU, AHB and APB buses clocks 200 | */ 201 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 202 | |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 203 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 204 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 205 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; 206 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 207 | 208 | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) 209 | { 210 | Error_Handler(); 211 | } 212 | } 213 | 214 | /** 215 | * @brief USART1 Initialization Function 216 | * @param None 217 | * @retval None 218 | */ 219 | static void MX_USART1_UART_Init(void) 220 | { 221 | 222 | /* USER CODE BEGIN USART1_Init 0 */ 223 | 224 | /* USER CODE END USART1_Init 0 */ 225 | 226 | /* USER CODE BEGIN USART1_Init 1 */ 227 | 228 | /* USER CODE END USART1_Init 1 */ 229 | huart1.Instance = USART1; 230 | huart1.Init.BaudRate = 9600; 231 | huart1.Init.WordLength = UART_WORDLENGTH_8B; 232 | huart1.Init.StopBits = UART_STOPBITS_1; 233 | huart1.Init.Parity = UART_PARITY_NONE; 234 | huart1.Init.Mode = UART_MODE_TX_RX; 235 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 236 | huart1.Init.OverSampling = UART_OVERSAMPLING_16; 237 | if (HAL_UART_Init(&huart1) != HAL_OK) 238 | { 239 | Error_Handler(); 240 | } 241 | /* USER CODE BEGIN USART1_Init 2 */ 242 | 243 | /* USER CODE END USART1_Init 2 */ 244 | 245 | } 246 | 247 | /** 248 | * @brief USART2 Initialization Function 249 | * @param None 250 | * @retval None 251 | */ 252 | static void MX_USART2_UART_Init(void) 253 | { 254 | 255 | /* USER CODE BEGIN USART2_Init 0 */ 256 | 257 | /* USER CODE END USART2_Init 0 */ 258 | 259 | /* USER CODE BEGIN USART2_Init 1 */ 260 | 261 | /* USER CODE END USART2_Init 1 */ 262 | huart2.Instance = USART2; 263 | huart2.Init.BaudRate = 115200; 264 | huart2.Init.WordLength = UART_WORDLENGTH_8B; 265 | huart2.Init.StopBits = UART_STOPBITS_1; 266 | huart2.Init.Parity = UART_PARITY_NONE; 267 | huart2.Init.Mode = UART_MODE_TX_RX; 268 | huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; 269 | huart2.Init.OverSampling = UART_OVERSAMPLING_16; 270 | if (HAL_UART_Init(&huart2) != HAL_OK) 271 | { 272 | Error_Handler(); 273 | } 274 | /* USER CODE BEGIN USART2_Init 2 */ 275 | 276 | /* USER CODE END USART2_Init 2 */ 277 | 278 | } 279 | 280 | /** 281 | * @brief GPIO Initialization Function 282 | * @param None 283 | * @retval None 284 | */ 285 | static void MX_GPIO_Init(void) 286 | { 287 | GPIO_InitTypeDef GPIO_InitStruct = {0}; 288 | 289 | /* GPIO Ports Clock Enable */ 290 | __HAL_RCC_GPIOC_CLK_ENABLE(); 291 | __HAL_RCC_GPIOH_CLK_ENABLE(); 292 | __HAL_RCC_GPIOA_CLK_ENABLE(); 293 | __HAL_RCC_GPIOB_CLK_ENABLE(); 294 | 295 | /*Configure GPIO pin Output Level */ 296 | HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET); 297 | 298 | /*Configure GPIO pin : B1_Pin */ 299 | GPIO_InitStruct.Pin = B1_Pin; 300 | GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; 301 | GPIO_InitStruct.Pull = GPIO_NOPULL; 302 | HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct); 303 | 304 | /*Configure GPIO pin : LD2_Pin */ 305 | GPIO_InitStruct.Pin = LD2_Pin; 306 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 307 | GPIO_InitStruct.Pull = GPIO_NOPULL; 308 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; 309 | HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct); 310 | 311 | } 312 | 313 | /* USER CODE BEGIN 4 */ 314 | 315 | /* 316 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 317 | The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 318 | a byte string of which the beginning has been passed to the function. The 319 | number of bytes to check is also a parameter. 320 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 321 | */ 322 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes) { 323 | 324 | uint16_t crc; 325 | const unsigned char *ptr; 326 | size_t a; 327 | 328 | if (!crc_tab16_init) init_crc16_tab(); 329 | 330 | crc = CRC_START_MODBUS; 331 | ptr = input_str; 332 | 333 | if (ptr != NULL) for (a = 0; a < num_bytes; a++) { 334 | 335 | crc = (crc >> 8) ^ crc_tab16[(crc ^ (uint16_t)*ptr++) & 0x00FF]; 336 | } 337 | 338 | return crc; 339 | 340 | } /* crc_modbus */ 341 | 342 | /* 343 | static void init_crc16_tab( void ); 344 | For optimal performance uses the CRC16 routine a lookup table with values 345 | that can be used directly in the XOR arithmetic in the algorithm. This 346 | lookup table is calculated by the init_crc16_tab() routine, the first time 347 | the CRC function is called. 348 | Ref: Lammert Bies, https://github.com/lammertb/libcrc/blob/master/src/crc16.c 349 | */ 350 | static void init_crc16_tab(void) { 351 | 352 | uint16_t i; 353 | uint16_t j; 354 | uint16_t crc; 355 | uint16_t c; 356 | 357 | for (i = 0; i < 256; i++) { 358 | 359 | crc = 0; 360 | c = i; 361 | 362 | for (j = 0; j < 8; j++) { 363 | 364 | if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16; 365 | else crc = crc >> 1; 366 | 367 | c = c >> 1; 368 | } 369 | 370 | crc_tab16[i] = crc; 371 | } 372 | 373 | crc_tab16_init = true; 374 | 375 | } /* init_crc16_tab */ 376 | 377 | /* USER CODE END 4 */ 378 | 379 | /** 380 | * @brief This function is executed in case of error occurrence. 381 | * @retval None 382 | */ 383 | void Error_Handler(void) 384 | { 385 | /* USER CODE BEGIN Error_Handler_Debug */ 386 | /* User can add his own implementation to report the HAL error return state */ 387 | __disable_irq(); 388 | while (1) 389 | { 390 | } 391 | /* USER CODE END Error_Handler_Debug */ 392 | } 393 | 394 | #ifdef USE_FULL_ASSERT 395 | /** 396 | * @brief Reports the name of the source file and the source line number 397 | * where the assert_param error has occurred. 398 | * @param file: pointer to the source file name 399 | * @param line: assert_param error line source number 400 | * @retval None 401 | */ 402 | void assert_failed(uint8_t *file, uint32_t line) 403 | { 404 | /* USER CODE BEGIN 6 */ 405 | /* User can add his own implementation to report the file name and line number, 406 | ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 407 | /* USER CODE END 6 */ 408 | } 409 | #endif /* USE_FULL_ASSERT */ 410 | -------------------------------------------------------------------------------- /library/checksum.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Library: libcrc 3 | * File: include/checksum.h 4 | * Author: Lammert Bies 5 | * 6 | * This file is licensed under the MIT License as stated below 7 | * 8 | * Copyright (c) 1999-2018 Lammert Bies 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * Description 29 | * ----------- 30 | * The headerfile include/checksum.h contains the definitions and prototypes 31 | * for routines that can be used to calculate several kinds of checksums. 32 | */ 33 | 34 | #ifndef DEF_LIBCRC_CHECKSUM_H 35 | #define DEF_LIBCRC_CHECKSUM_H 36 | 37 | #include 38 | #include 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | /* 45 | * #define CRC_POLY_xxxx 46 | * 47 | * The constants of the form CRC_POLY_xxxx define the polynomials for some well 48 | * known CRC calculations. 49 | */ 50 | 51 | #define CRC_POLY_16 0xA001 52 | #define CRC_POLY_32 0xEDB88320ul 53 | #define CRC_POLY_64 0x42F0E1EBA9EA3693ull 54 | #define CRC_POLY_CCITT 0x1021 55 | #define CRC_POLY_DNP 0xA6BC 56 | #define CRC_POLY_KERMIT 0x8408 57 | #define CRC_POLY_SICK 0x8005 58 | 59 | /* 60 | * #define CRC_START_xxxx 61 | * 62 | * The constants of the form CRC_START_xxxx define the values that are used for 63 | * initialization of a CRC value for common used calculation methods. 64 | */ 65 | 66 | #define CRC_START_8 0x00 67 | #define CRC_START_16 0x0000 68 | #define CRC_START_MODBUS 0xFFFF 69 | #define CRC_START_XMODEM 0x0000 70 | #define CRC_START_CCITT_1D0F 0x1D0F 71 | #define CRC_START_CCITT_FFFF 0xFFFF 72 | #define CRC_START_KERMIT 0x0000 73 | #define CRC_START_SICK 0x0000 74 | #define CRC_START_DNP 0x0000 75 | #define CRC_START_32 0xFFFFFFFFul 76 | #define CRC_START_64_ECMA 0x0000000000000000ull 77 | #define CRC_START_64_WE 0xFFFFFFFFFFFFFFFFull 78 | 79 | /* 80 | * Prototype list of global functions 81 | */ 82 | 83 | unsigned char * checksum_NMEA(const unsigned char *input_str, unsigned char *result); 84 | uint8_t crc_8(const unsigned char *input_str, size_t num_bytes); 85 | uint16_t crc_16(const unsigned char *input_str, size_t num_bytes); 86 | uint32_t crc_32(const unsigned char *input_str, size_t num_bytes); 87 | uint64_t crc_64_ecma(const unsigned char *input_str, size_t num_bytes); 88 | uint64_t crc_64_we(const unsigned char *input_str, size_t num_bytes); 89 | uint16_t crc_ccitt_1d0f(const unsigned char *input_str, size_t num_bytes); 90 | uint16_t crc_ccitt_ffff(const unsigned char *input_str, size_t num_bytes); 91 | uint16_t crc_dnp(const unsigned char *input_str, size_t num_bytes); 92 | uint16_t crc_kermit(const unsigned char *input_str, size_t num_bytes); 93 | uint16_t crc_modbus(const unsigned char *input_str, size_t num_bytes); 94 | uint16_t crc_sick(const unsigned char *input_str, size_t num_bytes); 95 | uint16_t crc_xmodem(const unsigned char *input_str, size_t num_bytes); 96 | uint8_t update_crc_8(uint8_t crc, unsigned char c); 97 | uint16_t update_crc_16(uint16_t crc, unsigned char c); 98 | uint32_t update_crc_32(uint32_t crc, unsigned char c); 99 | uint64_t update_crc_64_ecma(uint64_t crc, unsigned char c); 100 | uint16_t update_crc_ccitt(uint16_t crc, unsigned char c); 101 | uint16_t update_crc_dnp(uint16_t crc, unsigned char c); 102 | uint16_t update_crc_kermit(uint16_t crc, unsigned char c); 103 | uint16_t update_crc_sick(uint16_t crc, unsigned char c, unsigned char prev_byte); 104 | 105 | /* 106 | * Global CRC lookup tables 107 | */ 108 | 109 | extern const uint32_t crc_tab32[]; 110 | extern const uint64_t crc_tab64[]; 111 | 112 | #ifdef __cplusplus 113 | }// Extern C 114 | #endif 115 | 116 | #endif // DEF_LIBCRC_CHECKSUM_H 117 | -------------------------------------------------------------------------------- /library/crc16.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Library: libcrc 3 | * File: src/crc16.c 4 | * Author: Lammert Bies 5 | * 6 | * This file is licensed under the MIT License as stated below 7 | * 8 | * Copyright (c) 1999-2016 Lammert Bies 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * Description 29 | * ----------- 30 | * The source file src/crc16.c contains routines which calculate the common 31 | * CRC16 cyclic redundancy check values for an incomming byte string. 32 | */ 33 | 34 | #include 35 | #include 36 | #include "checksum.h" 37 | 38 | static void init_crc16_tab( void ); 39 | 40 | static bool crc_tab16_init = false; 41 | static uint16_t crc_tab16[256]; 42 | 43 | /* 44 | * uint16_t crc_16( const unsigned char *input_str, size_t num_bytes ); 45 | * 46 | * The function crc_16() calculates the 16 bits CRC16 in one pass for a byte 47 | * string of which the beginning has been passed to the function. The number of 48 | * bytes to check is also a parameter. The number of the bytes in the string is 49 | * limited by the constant SIZE_MAX. 50 | */ 51 | 52 | uint16_t crc_16( const unsigned char *input_str, size_t num_bytes ) { 53 | 54 | uint16_t crc; 55 | const unsigned char *ptr; 56 | size_t a; 57 | 58 | if ( ! crc_tab16_init ) init_crc16_tab(); 59 | 60 | crc = CRC_START_16; 61 | ptr = input_str; 62 | 63 | if ( ptr != NULL ) for (a=0; a> 8) ^ crc_tab16[ (crc ^ (uint16_t) *ptr++) & 0x00FF ]; 66 | } 67 | 68 | return crc; 69 | 70 | } /* crc_16 */ 71 | 72 | /* 73 | * uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ); 74 | * 75 | * The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for 76 | * a byte string of which the beginning has been passed to the function. The 77 | * number of bytes to check is also a parameter. 78 | */ 79 | 80 | uint16_t crc_modbus( const unsigned char *input_str, size_t num_bytes ) { 81 | 82 | uint16_t crc; 83 | const unsigned char *ptr; 84 | size_t a; 85 | 86 | if ( ! crc_tab16_init ) init_crc16_tab(); 87 | 88 | crc = CRC_START_MODBUS; 89 | ptr = input_str; 90 | 91 | if ( ptr != NULL ) for (a=0; a> 8) ^ crc_tab16[ (crc ^ (uint16_t) *ptr++) & 0x00FF ]; 94 | } 95 | 96 | return crc; 97 | 98 | } /* crc_modbus */ 99 | 100 | /* 101 | * uint16_t update_crc_16( uint16_t crc, unsigned char c ); 102 | * 103 | * The function update_crc_16() calculates a new CRC-16 value based on the 104 | * previous value of the CRC and the next byte of data to be checked. 105 | */ 106 | 107 | uint16_t update_crc_16( uint16_t crc, unsigned char c ) { 108 | 109 | if ( ! crc_tab16_init ) init_crc16_tab(); 110 | 111 | return (crc >> 8) ^ crc_tab16[ (crc ^ (uint16_t) c) & 0x00FF ]; 112 | 113 | } /* update_crc_16 */ 114 | 115 | /* 116 | * static void init_crc16_tab( void ); 117 | * 118 | * For optimal performance uses the CRC16 routine a lookup table with values 119 | * that can be used directly in the XOR arithmetic in the algorithm. This 120 | * lookup table is calculated by the init_crc16_tab() routine, the first time 121 | * the CRC function is called. 122 | */ 123 | 124 | static void init_crc16_tab( void ) { 125 | 126 | uint16_t i; 127 | uint16_t j; 128 | uint16_t crc; 129 | uint16_t c; 130 | 131 | for (i=0; i<256; i++) { 132 | 133 | crc = 0; 134 | c = i; 135 | 136 | for (j=0; j<8; j++) { 137 | 138 | if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ CRC_POLY_16; 139 | else crc = crc >> 1; 140 | 141 | c = c >> 1; 142 | } 143 | 144 | crc_tab16[i] = crc; 145 | } 146 | 147 | crc_tab16_init = true; 148 | 149 | } /* init_crc16_tab */ 150 | --------------------------------------------------------------------------------