飞道的博客

STM32-modbus rtu 之主机程序

380人阅读  评论(0)

STM32-modbus rtu 之主机程序

 

一、STM32串口的发送与接收

考虑到modbus的使用场合大多为半双工而非全双工,所以,串口接收采用DMA+空闲中断,发送则直接发送。


  
  1. #include "serial.h"
  2. #include "string.h"
  3.  
  4. _serialbuf_st serialRXbuf_st;
  5. _serialbuf_st serialTXbuf_st;
  6.  
  7. /*DMA接收数据缓存*/
  8. u8 g_uart1DmaRXBuf[UART_DMARX_SIZE];
  9.     
  10. /*
  11. 说明:3个串口直接发送函数
  12. 编写:林
  13. */
  14. void myUSART_Sendbyte(USART_TypeDef* USARTx, uint16_t Data)
  15. {
  16.     while((USARTx->SR& 0X40)== 0); 
  17.     USARTx->DR = (Data & ( uint16_t) 0x01FF);
  18. }
  19. void myUSART_Sendstr(USART_TypeDef* USARTx, const char  *s)
  20. {
  21.     while(*s != '\0')
  22.     {      
  23.         myUSART_Sendbyte( USARTx, *s) ;
  24.         s++;
  25.     }
  26. }
  27. void myUSART_Sendarr(USART_TypeDef* USARTx, u8 a[] ,uint8_t len)
  28. {
  29.     uint8_t i= 0;
  30.     while(i <  len )
  31.     {      
  32.         myUSART_Sendbyte( USARTx, a[i]) ;
  33.         i++;
  34.     }
  35. }
  36. /*
  37. 说明:
  38.   串口1初始化
  39.   串口1使用DMA 接收 
  40. 编写:林
  41. */
  42. void Usart1_init(u32 baud)
  43. {    
  44.     GPIO_InitTypeDef GPIO_InitStructure;
  45.     USART_InitTypeDef  USART_InitStructure;
  46.     DMA_InitTypeDef DMA_InitStructure;
  47.     NVIC_InitTypeDef NVIC_InitStructure;
  48.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE);
  49.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  
  50.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
  51.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  52.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  53.     GPIO_Init(GPIOA, &GPIO_InitStructure);
  54.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  55.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  56.     GPIO_Init(GPIOA, &GPIO_InitStructure);
  57.     USART_InitStructure.USART_BaudRate =baud; //一秒发送BaudRate个bit
  58.     USART_InitStructure.USART_WordLength =USART_WordLength_8b;
  59.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  60.     USART_InitStructure.USART_Parity = USART_Parity_No;
  61.     USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None;
  62.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  63.     USART_Init(USART1, &USART_InitStructure); 
  64.     DMA_DeInit(DMA1_Channel5);  
  65.     DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(USART1->DR));  
  66.     DMA_InitStructure.DMA_MemoryBaseAddr = (u32)g_uart1DmaRXBuf;  
  67.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  
  68.     DMA_InitStructure.DMA_BufferSize =  UART_DMARX_SIZE;  
  69.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  
  70.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   
  71.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  
  72.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  
  73.     DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  
  74.     DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;  
  75.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  
  76.     DMA_Init(DMA1_Channel5,&DMA_InitStructure);  
  77.     USART_ITConfig(USART1,USART_IT_TC,DISABLE);  
  78.     USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);  
  79.     USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); 
  80.          
  81.     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;               //   
  82.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;       //    
  83.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;               //    
  84.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                 //     
  85.     NVIC_Init(&NVIC_InitStructure);     
  86.                                             
  87.     USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  
  88.     USART_Cmd(USART1, ENABLE);     
  89.     DMA_Cmd(DMA1_Channel5,ENABLE);  
  90.     memset(   & serialRXbuf_st , 0, sizeof (   serialRXbuf_st ) ) ;
  91. }

  
  1. //等待发送完成
  2. void WaitForTransmitComplete(USART_TypeDef* USARTx)
  3. {
  4.     while((USARTx->SR& 0X40)== 0){}; 
  5. }
  6.  
  7. /*
  8. 说明:串口中断,DMA与空闲中断处理,用于串口接收
  9. 编写:林
  10. */
  11. void USART1_IRQHandler(void)
  12.     _serialbuf_st *p= &serialRXbuf_st;
  13.     __IO u8 temp = 0;
  14.     u8 i= 0;
  15.     if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
  16.     {
  17.         temp = USART1->SR;
  18.         temp = USART1->DR; 
  19.         DMA_Cmd(DMA1_Channel5,DISABLE);
  20.         temp = UART_DMARX_SIZE - (( uint16_t)(DMA1_Channel5->CNDTR));
  21.         
  22.         for (i = 0;i < temp;i++)
  23.         {
  24.               p->buf[i] =g_uart1DmaRXBuf[i];
  25.         }
  26.         p->len = temp ;
  27.         
  28.         DMA_SetCurrDataCounter(DMA1_Channel5,UART_DMARX_SIZE);
  29.         DMA_Cmd(DMA1_Channel5,ENABLE);
  30.     } 
  31.     __nop(); 
  32. }

  
  1. /*serial.h*/
  2. #ifndef __SERIALx_H
  3. #define __SERIALx_H    
  4. #include "stm32f10x.h"
  5. /*DMA接收数据缓存大小*/
  6. #define UART_DMARX_SIZE 0xff
  7. typedef struct  
  8. {
  9.     u8 buf[UART_DMARX_SIZE];
  10.     __IO u8 len;
  11. } _serialbuf_st ;   //串口数据结构
  12. typedef struct  
  13. {
  14.     u8 addr; //从机地址
  15.     u8 start; //寄存器起始
  16.     u8 len;   //接收到或待发送的寄存器数
  17.     u16 buf[UART_DMARX_SIZE/ 2]; //寄存器数据
  18. } _mbdata_st; //用户数据
  19. extern _serialbuf_st serialRXbuf_st;
  20. extern _serialbuf_st serialTXbuf_st;
  21. void Usart1_init(u32 baud) ;
  22. void WaitForTransmitComplete(USART_TypeDef* USARTx) ;
  23. void myUSART_Sendbyte(USART_TypeDef* USARTx, uint16_t Data) ;
  24. void myUSART_Sendstr(USART_TypeDef* USARTx, const char  *s) ;
  25. void myUSART_Sendarr(USART_TypeDef* USARTx, u8 a[] ,uint8_t len);
  26. #endif

 

二、实现读保持寄存器功能:F=0x03

首先实现发送函数


  
  1. /*
  2. 说明:
  3. 接收“读保持寄存器”的结果
  4. 命令0X03
  5. 返回:
  6. res_OK 正确
  7. res_ERR1 其他错误
  8. res_ERR2 地址不符
  9. res_ERR3 无反馈
  10. */
  11. u8 mb_recv_readHoldingReg( _mbdata_st *mbp)
  12.     u8 i;
  13.     if( serialRXbuf_st.len == 0 ) return res_ERR3;
  14.     serialRXbuf_st.len= 0;
  15.     if( mbp->addr == serialRXbuf_st.buf[ 0] )
  16.     {
  17.           if( 0x03 == serialRXbuf_st.buf[ 1] )
  18.          { 
  19.               for(i= 0;i<serialRXbuf_st.buf[ 2]/ 2;i++)
  20.              {
  21.                     mbp->buf[mbp->start +i]= (u16)(serialRXbuf_st.buf[i* 2+ 3]>> 8) + serialRXbuf_st.buf[i* 2+ 4];
  22.              }
  23.              mbp->len =  serialRXbuf_st.buf [ 2]/ 2; //寄存器数
  24.               return res_OK;
  25.          }
  26.           else
  27.          {
  28.               return res_ERR1;
  29.          }
  30.     }
  31.     return res_ERR2; 
  32. }

 

三、实现写保持寄存器功能:F=0X10

发送与接收代码如下


  
  1. /*
  2. 发送"写保持寄存器",命令0X10
  3. */
  4. void mb_sent_writeHoldingReg( const _mbdata_st  mbp)
  5.     u8 i= 0;
  6.     u16 temp;
  7.     u8 len = mbp.len; 
  8.     if(len> 0x7d)len= 0x7d;
  9.     serialTXbuf_st.buf[ 0] = mbp.addr;
  10.     serialTXbuf_st.buf[ 1] = 0x10;
  11.     serialTXbuf_st.buf[ 2] = mbp.start>> 8;
  12.     serialTXbuf_st.buf[ 3] = mbp.start;
  13.     serialTXbuf_st.buf[ 4] = 0;
  14.     serialTXbuf_st.buf[ 5] =  len;
  15.     serialTXbuf_st.buf[ 6] =  len* 2;
  16.     for(;i<mbp.len;i++)
  17.     {
  18.       serialTXbuf_st.buf[i* 2+ 7] = mbp.buf[i]>> 8;
  19.       serialTXbuf_st.buf[i* 2+ 8] = mbp.buf[i];
  20.     }   
  21.     temp=usMBCRC16(  serialTXbuf_st.buf,   len* 2+ 7 );
  22.     serialTXbuf_st.buf[ len* 2+ 7] = temp;     //低
  23.     serialTXbuf_st.buf[ len* 2+ 8] = temp>> 8;
  24.     myUSART_Sendarr(  USART1,   serialTXbuf_st.buf ,   len* 2+ 9) ;
  25.     WaitForTransmitComplete(USART1) ; //发送完成
  26.  }
  27. /*
  28. 说明:
  29.     接收“写保持寄存器”的从机反馈
  30. 返回:
  31.     res_OK 正确
  32.     res_ERR1 校验错误
  33.     res_ERR2 返回格式错误
  34.     res_ERR3 无反馈
  35. */
  36. u8 mb_recv_writeHoldingReg( _mbdata_st  *mbp )
  37. {
  38.     u8 i= 0;
  39.     if( serialRXbuf_st.len == 0 ) return res_ERR3;
  40.     serialRXbuf_st.len= 0;
  41.     for(i= 0;i< 6;i++)
  42.     {
  43.         if ( serialTXbuf_st.buf[i] != serialRXbuf_st.buf[i] ) return res_ERR2;
  44.     }
  45.     if( serialRXbuf_st.buf[ 6] + (u16)(serialRXbuf_st.buf[ 7]<< 8) != usMBCRC16(  serialRXbuf_st.buf,   6 )) return res_ERR1;
  46.     return res_OK;
  47. }

 

 

 

四、程序调用

为了方便,将上面函数统一起来


  
  1. u8 gmod = 0 ; //测试用,gmod=0,测试写保持寄存器功能,gmod=1读保持寄存器功能。
  2. _mbdata_st HoldingReg_st = { 1, 0, 5,{ 1, 2, 3, 4, 5, 6, 7, 8, 9}};
  3. u8 gsync = 1 ;
  4. void mb_setMODRXorTX(bool RxorTx)
  5. {
  6. //此处需修改硬件,用于使用外部器件(比如485器件)的接收或发送。
  7. }
  8. //统一发送
  9. void smb_sentHoldingReg(const _mbdata_st  mbp   )
  10. { mb_setMODRXorTX( 1); //改为发送模式
  11.       if( gmod == 1 )
  12.         mb_sent_writeHoldingReg( HoldingReg_st);
  13.       else 
  14.        mb_sent_readHoldingReg( HoldingReg_st );
  15.      mb_setMODRXorTX( 0); //改为接收模式
  16.      gsync= 1;
  17. }
  18. //统一接收
  19.   u8 smb_recvHoldingReg( _mbdata_st  *mbp   )
  20. {
  21.     u8 rel= 0xff;
  22.     if( 1 == gsync) //发送完成
  23.     {
  24.          gsync= 0;
  25.           while( serialRXbuf_st.len == 0
  26.          {
  27.             if(TIM3->CNT > 4900)   break ; //从机无响应
  28.          }; //接收完成
  29.          
  30.           if( gmod == 1)
  31.          {
  32.             rel=mb_recv_writeHoldingReg( &HoldingReg_st ) ;
  33.          } else 
  34.          {
  35.             rel=mb_recv_readHoldingReg(   &HoldingReg_st ) ;
  36.          }
  37.     }  
  38.     return rel ;
  39. }

在定时器服务里调用发送函数


  
  1. void TIM3_IRQHandler(void)   //TIM3中断
  2. {
  3.     if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
  4.     {
  5.         TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );   //清除TIMx的中断待处理位:TIM 中断源 
  6.         {
  7.               if(gmod == 1) HoldingReg_st.buf [ 1]++ ; //累增某个保持寄存器,用于测试观察
  8.              smb_sentHoldingReg( HoldingReg_st ) ;
  9.         }
  10.     }
  11. }

在主循环里调用接收函数


  
  1.     while( 1)
  2.     {
  3.           rel = smb_recvHoldingReg( &HoldingReg_st ) ;
  4.           if( res_OK == rel && HoldingReg_st.buf[ 0] == 1 ) LED0=!LED0; //观察结果
  5.     }

 

 

 

五、验证

程序烧录到STM32,,串口连接电脑,使用PC端从机软件 Modbus Slave 观察

 

F=0X03

F=0X10,(虽然显示03)

这个从机软件不能显示F=0X10也就是16,但功能是可以使用的。

不过这毕竟令人心里不爽,所以我使用nmodbus类库编写了C#上位机软件进行验证,如下图所示,可见F=16也就是0X10

 

 


转载:https://blog.csdn.net/wangzibigan/article/details/77722939
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场