mbascii.c 15 KB

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