modbus是一個非常好的序列槽協定(當然也能用在網口上),它簡潔、規範、強大。可以滿足大部分的工業、嵌入式需求。
這裡詳細說下如何将freemodbus移植到stm32平台。我之前下載下傳的版本是1.5,目前官網最新的版本是1.6。兩者差别不大,這裡以1.5版本做示範。
1、下載下傳
下載下傳好之後,解壓得到如下内容:
我們需要的是modbus這個檔案夾,和demo->BARE下的port檔案夾。
2、準備一個STM32的工程檔案夾
在工程檔案夾下建立一個檔案夾:FreeModbus。将第一步擷取的兩個檔案夾放到裡面。
打開工程,添加兩個group,名字分别為modbus和port。将這兩個檔案夾下的C檔案都添加進來,tcp相關的除外。
檔案包含路徑,也添加這幾個檔案夾的位置:
3、完善portserial.c檔案
該檔案就是modbus通信中用到的序列槽的初始化配置檔案。我這裡選擇usart1,波特率9600.
第一次打開這個檔案,内容如下:
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
return FALSE;
}
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
return TRUE;
}
認真看一下函數名字,你會發現這些函數分别是:序列槽使能、序列槽初始化、發送一個位元組、接收一個位元組等等。
完善後代碼如下:
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if(xRxEnable == TRUE)
{
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
else
{
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
}
if(xTxEnable == TRUE)
{
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
}
else
{
USART_ITConfig(USART1, USART_IT_TC, DISABLE);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
USART1_Config((uint16_t)ulBaudRate);
USART_NVIC();
return TRUE;
}
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
USART_SendData(USART1, ucByte);
return TRUE;
}
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = USART_ReceiveData(USART1);
return TRUE;
}
static void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
static void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
prvvUARTRxISR();
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
if(USART_GetITStatus(USART1, USART_IT_ORE) == SET)
{
USART_ClearITPendingBit(USART1, USART_IT_ORE);
prvvUARTRxISR();
}
if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
{
prvvUARTTxReadyISR();
USART_ClearITPendingBit(USART1, USART_IT_TC);
}
}
其中USART1_Config((uint16_t)ulBaudRate);和 USART_NVIC();是序列槽初始化的代碼,如下:
void USART1_Config(uint16_t buad)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = buad;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void USART_NVIC(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
4、完善porttimer.c檔案
modbus工作時需要一個定時器,是以這裡配置一個定時器。定時器時基是50us,周期做為參數輸入。代碼如下:
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
timer2_init(usTim1Timerout50us);
timer2_nvic();
return TRUE;
}
void vMBPortTimersEnable( )
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_SetCounter(TIM2,0x0000);
TIM_Cmd(TIM2, ENABLE);
}
void vMBPortTimersDisable( )
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
TIM_SetCounter(TIM2,0x0000);
TIM_Cmd(TIM2, DISABLE);
}
static void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
prvvTIMERExpiredISR();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
其中 timer2_init(usTim1Timerout50us) 和 timer2_nvic() 是timer2初始化函數,内容如下:
void timer2_init(uint16_t period)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Period = period;
TIM_TimeBaseStructure.TIM_Prescaler = (1800 - 1);
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_Cmd(TIM2, ENABLE);
}
void timer2_nvic(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
5、在main.c檔案中,定義各個模拟寄存器的位址和大小。
#define REG_INPUT_START 0x0000
#define REG_INPUT_NREGS 8
#define REG_HOLDING_START 0x0000
#define REG_HOLDING_NREGS 8
#define REG_COILS_START 0x0000
#define REG_COILS_SIZE 16
#define REG_DISCRETE_START 0x0000
#define REG_DISCRETE_SIZE 16
6
補全輸入寄存器操作函數、保持寄存器操作函數
modbus功能進行初始化,設定位址和波特率。這部分内容可以參考官方資料裡的例程,也可以直接複制别人寫好的。我這裡放别人寫好的代碼:
int main(void)
{
usRegInputBuf[0] = 'I';
usRegInputBuf[1] = ' ';
usRegInputBuf[2] = 'a';
usRegInputBuf[3] = 'm';
usRegInputBuf[4] = ' ';
usRegInputBuf[5] = 'I';
RCC_Config();
eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
eMBEnable();
for(;;)
{
(void)eMBPoll();
}
}
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( ( usAddress >= REG_INPUT_START )
&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegInputStart );
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if((usAddress >= REG_HOLDING_START)&&\
((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
{
iRegIndex = (int)(usAddress - usRegHoldingStart);
switch(eMode)
{
case MB_REG_READ:
while(usNRegs > 0)
{
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while(usNRegs > 0)
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if((usAddress >= REG_HOLDING_START)&&\
((usAddress+usNCoils) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
{
iRegIndex = (int)(usAddress - usRegHoldingStart);
switch(eMode)
{
case MB_REG_READ:
while(usNCoils > 0)
{
iRegIndex++;
usNCoils--;
}
break;
case MB_REG_WRITE:
while(usNCoils > 0)
{
iRegIndex++;
usNCoils--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
( void )pucRegBuffer;
( void )usAddress;
( void )usNDiscrete;
return MB_ENOREG;
}
7、修改mbrtu.c檔案
否則modbus從機收到指令後,隻會傳回一次資料。在函數“eMBRTUSend”中。
eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
eMBErrorCode eStatus = MB_ENOERR;
USHORT usCRC16;
ENTER_CRITICAL_SECTION( );
if( eRcvState == STATE_RX_IDLE )
{
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
usSndBufferCount = 1;
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
usSndBufferCount += usLength;
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
eSndState = STATE_TX_XMIT;
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++;
usSndBufferCount--;
vMBPortSerialEnable( FALSE, TRUE );
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
8、修改mbconfig.h檔案
取消對ASCII的支援。
#define MB_ASCII_ENABLED ( 0 )
9、儲存,編譯,下載下傳。使用專用的modbus工具測試
工具配置如下:
modbus指令格式如下:
咱們這裡設定如下:01 04 00 00 00 02,功能碼04,起始位址0,資料長度2.校驗碼沒有寫怎麼辦?
這就是這個工具的便利之處!我們不用管,它會自動計算!直接點選發送即可。得到結果如下:
可以看到下面的框裡,綠色的是我們發送的内容,最後兩位是工具自動補上的。藍色内容是單片機(也就是modbus從機)傳回給我們的。