mbascii.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /*
  2. * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
  3. * Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.at>
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions
  8. * are met:
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. The name of the author may not be used to endorse or promote products
  15. * derived from this software without specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  18. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  19. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  20. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  21. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  22. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. *
  28. */
  29. /* ----------------------- System includes ----------------------------------*/
  30. #include "stdlib.h"
  31. #include "string.h"
  32. /* ----------------------- Platform includes --------------------------------*/
  33. #include "port.h"
  34. /* ----------------------- Modbus includes ----------------------------------*/
  35. #include "mb.h"
  36. #include "mbconfig.h"
  37. #include "mbascii.h"
  38. #include "mbframe.h"
  39. #include "mbcrc.h"
  40. #include "mbport.h"
  41. #if MB_ASCII_ENABLED > 0
  42. /* ----------------------- Defines ------------------------------------------*/
  43. #define MB_ASCII_DEFAULT_CR '\r' /*!< Default CR character for Modbus ASCII. */
  44. #define MB_ASCII_DEFAULT_LF '\n' /*!< Default LF character for Modbus ASCII. */
  45. #define MB_SER_PDU_SIZE_MIN 3 /*!< Minimum size of a Modbus ASCII frame. */
  46. #define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus ASCII frame. */
  47. #define MB_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */
  48. #define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
  49. #define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
  50. /* ----------------------- Type definitions ---------------------------------*/
  51. typedef enum
  52. {
  53. STATE_RX_IDLE, /*!< Receiver is in idle state. */
  54. STATE_RX_RCV, /*!< Frame is beeing received. */
  55. STATE_RX_WAIT_EOF /*!< Wait for End of Frame. */
  56. } eMBRcvState;
  57. typedef enum
  58. {
  59. STATE_TX_IDLE, /*!< Transmitter is in idle state. */
  60. STATE_TX_START, /*!< Starting transmission (':' sent). */
  61. STATE_TX_DATA, /*!< Sending of data (Address, Data, LRC). */
  62. STATE_TX_END, /*!< End of transmission. */
  63. STATE_TX_NOTIFY /*!< Notify sender that the frame has been sent. */
  64. } eMBSndState;
  65. typedef enum
  66. {
  67. BYTE_HIGH_NIBBLE, /*!< Character for high nibble of byte. */
  68. BYTE_LOW_NIBBLE /*!< Character for low nibble of byte. */
  69. } eMBBytePos;
  70. /* ----------------------- Static functions ---------------------------------*/
  71. static UCHAR prvucMBCHAR2BIN( UCHAR ucCharacter );
  72. static UCHAR prvucMBBIN2CHAR( UCHAR ucByte );
  73. static UCHAR prvucMBLRC( UCHAR * pucFrame, USHORT usLen );
  74. /* ----------------------- Static variables ---------------------------------*/
  75. static volatile eMBSndState eSndState;
  76. static volatile eMBRcvState eRcvState;
  77. /* We reuse the Modbus RTU buffer because only one buffer is needed and the
  78. * RTU buffer is bigger. */
  79. extern volatile UCHAR ucRTUBuf[];
  80. static volatile UCHAR *ucASCIIBuf = ucRTUBuf;
  81. static volatile USHORT usRcvBufferPos;
  82. static volatile eMBBytePos eBytePos;
  83. static volatile UCHAR *pucSndBufferCur;
  84. static volatile USHORT usSndBufferCount;
  85. static volatile UCHAR ucLRC;
  86. static volatile UCHAR ucMBLFCharacter;
  87. /* ----------------------- Start implementation -----------------------------*/
  88. eMBErrorCode
  89. eMBASCIIInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
  90. {
  91. eMBErrorCode eStatus = MB_ENOERR;
  92. ( void )ucSlaveAddress;
  93. ENTER_CRITICAL_SECTION( );
  94. ucMBLFCharacter = MB_ASCII_DEFAULT_LF;
  95. if( xMBPortSerialInit( ucPort, ulBaudRate, 7, eParity ) != TRUE )
  96. {
  97. eStatus = MB_EPORTERR;
  98. }
  99. else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_SEC * 20000UL ) != TRUE )
  100. {
  101. eStatus = MB_EPORTERR;
  102. }
  103. EXIT_CRITICAL_SECTION( );
  104. return eStatus;
  105. }
  106. void
  107. eMBASCIIStart( void )
  108. {
  109. ENTER_CRITICAL_SECTION( );
  110. vMBPortSerialEnable( TRUE, FALSE );
  111. eRcvState = STATE_RX_IDLE;
  112. EXIT_CRITICAL_SECTION( );
  113. /* No special startup required for ASCII. */
  114. ( void )xMBPortEventPost( EV_READY );
  115. }
  116. void
  117. eMBASCIIStop( void )
  118. {
  119. ENTER_CRITICAL_SECTION( );
  120. vMBPortSerialEnable( FALSE, FALSE );
  121. vMBPortTimersDisable( );
  122. EXIT_CRITICAL_SECTION( );
  123. }
  124. eMBErrorCode
  125. eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
  126. {
  127. eMBErrorCode eStatus = MB_ENOERR;
  128. ENTER_CRITICAL_SECTION( );
  129. assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
  130. /* Length and CRC check */
  131. if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
  132. && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) )
  133. {
  134. /* Save the address field. All frames are passed to the upper layed
  135. * and the decision if a frame is used is done there.
  136. */
  137. *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];
  138. /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
  139. * size of address field and CRC checksum.
  140. */
  141. *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
  142. /* Return the start of the Modbus PDU to the caller. */
  143. *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
  144. }
  145. else
  146. {
  147. eStatus = MB_EIO;
  148. }
  149. EXIT_CRITICAL_SECTION( );
  150. return eStatus;
  151. }
  152. eMBErrorCode
  153. eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
  154. {
  155. eMBErrorCode eStatus = MB_ENOERR;
  156. UCHAR usLRC;
  157. ENTER_CRITICAL_SECTION( );
  158. /* Check if the receiver is still in idle state. If not we where too
  159. * slow with processing the received frame and the master sent another
  160. * frame on the network. We have to abort sending the frame.
  161. */
  162. if( eRcvState == STATE_RX_IDLE )
  163. {
  164. /* First byte before the Modbus-PDU is the slave address. */
  165. pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
  166. usSndBufferCount = 1;
  167. /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
  168. pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
  169. usSndBufferCount += usLength;
  170. /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */
  171. usLRC = prvucMBLRC( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
  172. ucASCIIBuf[usSndBufferCount++] = usLRC;
  173. /* Activate the transmitter. */
  174. eSndState = STATE_TX_START;
  175. vMBPortSerialEnable( FALSE, TRUE );
  176. }
  177. else
  178. {
  179. eStatus = MB_EIO;
  180. }
  181. EXIT_CRITICAL_SECTION( );
  182. return eStatus;
  183. }
  184. BOOL
  185. xMBASCIIReceiveFSM( void )
  186. {
  187. BOOL xNeedPoll = FALSE;
  188. UCHAR ucByte;
  189. UCHAR ucResult;
  190. assert( eSndState == STATE_TX_IDLE );
  191. ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );
  192. switch ( eRcvState )
  193. {
  194. /* A new character is received. If the character is a ':' the input
  195. * buffer is cleared. A CR-character signals the end of the data
  196. * block. Other characters are part of the data block and their
  197. * ASCII value is converted back to a binary representation.
  198. */
  199. case STATE_RX_RCV:
  200. /* Enable timer for character timeout. */
  201. vMBPortTimersEnable( );
  202. if( ucByte == ':' )
  203. {
  204. /* Empty receive buffer. */
  205. eBytePos = BYTE_HIGH_NIBBLE;
  206. usRcvBufferPos = 0;
  207. }
  208. else if( ucByte == MB_ASCII_DEFAULT_CR )
  209. {
  210. eRcvState = STATE_RX_WAIT_EOF;
  211. }
  212. else
  213. {
  214. ucResult = prvucMBCHAR2BIN( ucByte );
  215. switch ( eBytePos )
  216. {
  217. /* High nibble of the byte comes first. We check for
  218. * a buffer overflow here. */
  219. case BYTE_HIGH_NIBBLE:
  220. if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
  221. {
  222. ucASCIIBuf[usRcvBufferPos] = ( UCHAR )( ucResult << 4 );
  223. eBytePos = BYTE_LOW_NIBBLE;
  224. break;
  225. }
  226. else
  227. {
  228. /* not handled in Modbus specification but seems
  229. * a resonable implementation. */
  230. eRcvState = STATE_RX_IDLE;
  231. /* Disable previously activated timer because of error state. */
  232. vMBPortTimersDisable( );
  233. }
  234. break;
  235. case BYTE_LOW_NIBBLE:
  236. ucASCIIBuf[usRcvBufferPos] |= ucResult;
  237. usRcvBufferPos++;
  238. eBytePos = BYTE_HIGH_NIBBLE;
  239. break;
  240. }
  241. }
  242. break;
  243. case STATE_RX_WAIT_EOF:
  244. if( ucByte == ucMBLFCharacter )
  245. {
  246. /* Disable character timeout timer because all characters are
  247. * received. */
  248. vMBPortTimersDisable( );
  249. /* Receiver is again in idle state. */
  250. eRcvState = STATE_RX_IDLE;
  251. /* Notify the caller of eMBASCIIReceive that a new frame
  252. * was received. */
  253. xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
  254. }
  255. else if( ucByte == ':' )
  256. {
  257. /* Empty receive buffer and back to receive state. */
  258. eBytePos = BYTE_HIGH_NIBBLE;
  259. usRcvBufferPos = 0;
  260. eRcvState = STATE_RX_RCV;
  261. /* Enable timer for character timeout. */
  262. vMBPortTimersEnable( );
  263. }
  264. else
  265. {
  266. /* Frame is not okay. Delete entire frame. */
  267. eRcvState = STATE_RX_IDLE;
  268. }
  269. break;
  270. case STATE_RX_IDLE:
  271. if( ucByte == ':' )
  272. {
  273. /* Enable timer for character timeout. */
  274. vMBPortTimersEnable( );
  275. /* Reset the input buffers to store the frame. */
  276. usRcvBufferPos = 0;;
  277. eBytePos = BYTE_HIGH_NIBBLE;
  278. eRcvState = STATE_RX_RCV;
  279. }
  280. break;
  281. }
  282. return xNeedPoll;
  283. }
  284. BOOL
  285. xMBASCIITransmitFSM( void )
  286. {
  287. BOOL xNeedPoll = FALSE;
  288. UCHAR ucByte;
  289. assert( eRcvState == STATE_RX_IDLE );
  290. switch ( eSndState )
  291. {
  292. /* Start of transmission. The start of a frame is defined by sending
  293. * the character ':'. */
  294. case STATE_TX_START:
  295. ucByte = ':';
  296. xMBPortSerialPutByte( ( CHAR )ucByte );
  297. eSndState = STATE_TX_DATA;
  298. eBytePos = BYTE_HIGH_NIBBLE;
  299. break;
  300. /* Send the data block. Each data byte is encoded as a character hex
  301. * stream with the high nibble sent first and the low nibble sent
  302. * last. If all data bytes are exhausted we send a '\r' character
  303. * to end the transmission. */
  304. case STATE_TX_DATA:
  305. if( usSndBufferCount > 0 )
  306. {
  307. switch ( eBytePos )
  308. {
  309. case BYTE_HIGH_NIBBLE:
  310. ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur >> 4 ) );
  311. xMBPortSerialPutByte( ( CHAR ) ucByte );
  312. eBytePos = BYTE_LOW_NIBBLE;
  313. break;
  314. case BYTE_LOW_NIBBLE:
  315. ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur & 0x0F ) );
  316. xMBPortSerialPutByte( ( CHAR )ucByte );
  317. pucSndBufferCur++;
  318. eBytePos = BYTE_HIGH_NIBBLE;
  319. usSndBufferCount--;
  320. break;
  321. }
  322. }
  323. else
  324. {
  325. xMBPortSerialPutByte( MB_ASCII_DEFAULT_CR );
  326. eSndState = STATE_TX_END;
  327. }
  328. break;
  329. /* Finish the frame by sending a LF character. */
  330. case STATE_TX_END:
  331. xMBPortSerialPutByte( ( CHAR )ucMBLFCharacter );
  332. /* We need another state to make sure that the CR character has
  333. * been sent. */
  334. eSndState = STATE_TX_NOTIFY;
  335. break;
  336. /* Notify the task which called eMBASCIISend that the frame has
  337. * been sent. */
  338. case STATE_TX_NOTIFY:
  339. eSndState = STATE_TX_IDLE;
  340. xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
  341. /* Disable transmitter. This prevents another transmit buffer
  342. * empty interrupt. */
  343. vMBPortSerialEnable( TRUE, FALSE );
  344. eSndState = STATE_TX_IDLE;
  345. break;
  346. /* We should not get a transmitter event if the transmitter is in
  347. * idle state. */
  348. case STATE_TX_IDLE:
  349. /* enable receiver/disable transmitter. */
  350. vMBPortSerialEnable( TRUE, FALSE );
  351. break;
  352. }
  353. return xNeedPoll;
  354. }
  355. BOOL
  356. xMBASCIITimerT1SExpired( void )
  357. {
  358. switch ( eRcvState )
  359. {
  360. /* If we have a timeout we go back to the idle state and wait for
  361. * the next frame.
  362. */
  363. case STATE_RX_RCV:
  364. case STATE_RX_WAIT_EOF:
  365. eRcvState = STATE_RX_IDLE;
  366. break;
  367. default:
  368. assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) );
  369. break;
  370. }
  371. vMBPortTimersDisable( );
  372. /* no context switch required. */
  373. return FALSE;
  374. }
  375. static UCHAR
  376. prvucMBCHAR2BIN( UCHAR ucCharacter )
  377. {
  378. if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) )
  379. {
  380. return ( UCHAR )( ucCharacter - '0' );
  381. }
  382. else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) )
  383. {
  384. return ( UCHAR )( ucCharacter - 'A' + 0x0A );
  385. }
  386. else
  387. {
  388. return 0xFF;
  389. }
  390. }
  391. static UCHAR
  392. prvucMBBIN2CHAR( UCHAR ucByte )
  393. {
  394. if( ucByte <= 0x09 )
  395. {
  396. return ( UCHAR )( '0' + ucByte );
  397. }
  398. else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) )
  399. {
  400. return ( UCHAR )( ucByte - 0x0A + 'A' );
  401. }
  402. else
  403. {
  404. /* Programming error. */
  405. assert( 0 );
  406. }
  407. return '0';
  408. }
  409. static UCHAR
  410. prvucMBLRC( UCHAR * pucFrame, USHORT usLen )
  411. {
  412. UCHAR ucLRC = 0; /* LRC char initialized */
  413. while( usLen-- )
  414. {
  415. ucLRC += *pucFrame++; /* Add buffer byte without carry */
  416. }
  417. /* Return twos complement */
  418. ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) );
  419. return ucLRC;
  420. }
  421. #endif