序文
この実験では、STM32F407VET6チップが MCU として使用され、25MHz の外部クロック ソースが使用されます。
Ethernet PHY層チップはLAN8720Aで、FreeModbusを移植してModbusTCPネットワークポート通信を実現しています。
具体的な内容については、記事を参照してください: [HAL ライブラリ] STM32CubeMX 開発----STM32F407----ETH+LAN8720A+LWIP----ping
今回はFreeModbusのTCP機能をクライアント(スレーブ)として移植し、ネットワークポートTCP-Modbus通信を実現します。
1.FreeModbusソースコードのダウンロード
FreeModbus ソース コードのダウンロード リンク: https://www.embedded-experts.at/en/freemodbus-downloads/
クリックしてダウンロード
ソースコードパッケージは次のとおりです。
2. FreeModbus ソース コードの移植 ----新しい TCP 関数ファイルを作成します
今回の実験では、FreeModbus の TCP 機能を実現するために、 FreeModbus_TCPフォルダを新規作成し、必要なファイルをすべて移植します。
ステップ1
freemodbus-v1.6フォルダーを開き、 modbusフォルダーをクリックします。
ステップ2
modbusフォルダー内のすべてのファイルを、新しく作成したFreeModbus_TCPフォルダーに移行します。
ステップ3
freemodbus-v1.6\demo\STR71XTCPにあるポートファイルを、新しく作成したFreeModbus_TCPフォルダーに移植します。
ステップ4
移植の最終結果、新しく作成されたFreeModbus_TCPフォルダーの内容は次のとおりです。
3. FreeModbus ソース コードの移植 ---- TCP 関数ファイルが STM32 プロジェクト ファイルに移植されます。
今回はEthernet pingを実現できるSTM32F407プロジェクトを使用します。
プロジェクトのソース コード: STM32F407-ETH+LAN8720A+LWIP-オペレーティング システムなし-ping----プログラム ソース コード
ステップ1
FreeModbus_TCPフォルダーをプロジェクト ファイルにコピーします。
ステップ2
keil5 を使用してプロジェクトを開き、FreeModbus_TCPフォルダーにファイルをインポートします。
FreeModbus_TCPフォルダーのfunctionフォルダーにあるすべての .c ファイルを選択します。
FreeModbus_TCPフォルダー内のポートフォルダーのすべての .c ファイルを選択します。
FreeModbus_TCPフォルダー内のtcpフォルダーのすべての .c ファイルを選択します。FreeModbus_TCPフォルダー内の mb.c ファイル
を選択します。
最終的な結果は次のとおりです
ステップ3
魔法の杖をクリックし、C/C++を選択し、ファイル パスを追加します。
ファイルパスを追加
次のように結果を追加します
プログラムをコンパイルするといくつかのエラーが発生します。エラーを取り除くために以下のプログラムを編集します。
4. FreeModbus ソースコードの移植 ---- プログラムの編集
ステップ 1: mbconfig.h を変更する
MB_ASCII と MB_RTU をオフにし、MB_TCPをオンにします。
ステップ 2: port.h を変更する
27 行目: #include "71x_type.h"をコメントアウトします。
39 行目から 46 行目までのコメントを開きます。
具体的なコードは次のとおりです。
ステップ 3: portevent.c を変更する
元のプログラムを次のプログラムに置き換えます。
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue;
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{
xEventInQueue = FALSE;
return TRUE;
}
BOOL
xMBPortEventPost( eMBEventType eEvent )
{
xEventInQueue = TRUE;
eQueuedEvent = eEvent;
return TRUE;
}
BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
if( xEventInQueue )
{
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
ステップ 4: porttcp.c を変更する
24 行目に#include "string.h"を追加します。
43 行目に#define NETCONN_COPY 0x01を追加します。
120 行目と 135 行目のvPortEnterCritical( );をコメントアウトします。
148 行目のvMBPortEventClose();をコメントアウトします。
ステップ 5: mb.c を変更する
232 行目のENTER_CRITICAL_SECTION( );をコメントアウトします。
261 行目のEXIT_CRITICAL_SECTION( );をコメントアウトします。
ステップ 6: 新しいファイルを作成する
User_modbus_TCP.cファイル
#include <stdio.h>
#include <string.h>
#include "User_modbus_TCP.h"
#include "mb.h"
#include "mbutils.h"
void ModbusTCPInit(void)
{
eMBTCPInit(MODBUS_TCP_PORT);
eMBEnable();
}
void ModbusTCPDeInit(void)
{
eMBDisable();
eMBClose();
}
void ModbusTCPMain(void)
{
if (MB_ENOERR != eMBPoll())
{
ModbusTCPDeInit();
ModbusTCPInit();
}
}
//线圈
#define REG_Coils_START 1
#define REG_Coils_SIZE 10
uint8_t Coils_Data[REG_Coils_SIZE] = {
1,1,0,1,0,0,1,1,1,0};
/**
* @brief: 读线圈---01,写线圈---05
*
* @param pucRegBuffer 缓存指针
* @param usAddress 起始地址
* @param usNCoils 线圈数量
* @param eMode 读写模式
* @return eMBErrorCode 错误码
*/
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
{
uint16_t i = 0,byteOffset=0,bitOffset=0,RegIndex = usAddress - REG_Coils_START-1;
if ((usAddress >= REG_Coils_START)&&(usAddress + usNCoils <= REG_Coils_START + REG_Coils_SIZE+1))
{
if (MB_REG_READ == eMode)
{
for(i=0;i<usNCoils;i++)
{
byteOffset = i / 8;
bitOffset = i % 8;
xMBUtilSetBits(&pucRegBuffer[byteOffset], bitOffset, 1, Coils_Data[RegIndex+i]);
}
}
else
{
for(i=0;i<usNCoils;i++)
{
byteOffset = i / 8;
bitOffset = i % 8;
Coils_Data[RegIndex+i]=xMBUtilGetBits(&pucRegBuffer[byteOffset], bitOffset, 1);
}
}
}
else
{
return MB_ENOREG;
}
return MB_ENOERR;
}
//离散寄存器
#define REG_DISCRETE_START 10
#define REG_DISCRETE_SIZE 20
uint8_t Discrete_Data[REG_DISCRETE_SIZE] = {
1,1,0,1,0,0,1,1,1,0,1,0,0,1};
/**
* @brief:读离散寄存器---02
*
* @param pucRegBuffer 缓存指针
* @param usAddress 起始地址
* @param usNDiscrete 寄存器个数
* @return eMBErrorCode 返回错误码
*/
eMBErrorCode eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
uint16_t i = 0,byteOffset=0,bitOffset=0,RegIndex = usAddress - REG_DISCRETE_START-1;
if ((usAddress >= REG_DISCRETE_START)&&(usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE+1))
{
for(i=0;i<usNDiscrete;i++)
{
byteOffset = i / 8;
bitOffset = i % 8;
xMBUtilSetBits(&pucRegBuffer[byteOffset], bitOffset, 1, Discrete_Data[RegIndex+i]);
}
}
else
{
return MB_ENOREG;
}
return MB_ENOERR;
}
//保持寄存器
#define REG_HOLDING_REGISTER_START 10
#define REG_HOLDING_REGISTER_SIZE 30
uint16_t Holding_Data[REG_HOLDING_REGISTER_SIZE] =
{
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12};
/**
* @brief: 读保持寄存器---03,写保持寄存器---06
*
* @param pucRegBuffer 缓存指针
* @param usAddress 起始地址
* @param usNRegs 寄存器个数
* @param eMode 读写模式
* @return eMBErrorCode 返回错误码
*/
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
{
uint16_t i = 0,RegIndex = usAddress - REG_HOLDING_REGISTER_START-1;
if ((usAddress >= REG_HOLDING_REGISTER_START )&&(usAddress + usNRegs <= REG_HOLDING_REGISTER_START + REG_HOLDING_REGISTER_SIZE+1))
{
if (MB_REG_READ == eMode)//读
{
for(i=0;i<usNRegs;i++)
{
pucRegBuffer[i*2] = (UCHAR)(Holding_Data[RegIndex+i]>>8);
pucRegBuffer[i*2+1] = (UCHAR)Holding_Data[RegIndex+i];
}
}
else//写
{
for(i=0;i<usNRegs;i++)
{
Holding_Data[RegIndex+i]=(pucRegBuffer[i*2]<<8)|(pucRegBuffer[i*2+1]);
}
}
}
else
{
return MB_ENOREG;
}
return MB_ENOERR;
}
//输入寄存器
#define REG_INPUT_REGISTER_START 1
#define REG_INPUT_REGISTER_SIZE 20
uint16_t Input_Data[REG_DISCRETE_SIZE] =
{
100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119};
/**
* @brief: 读输入寄存器---04
*
* @param pucRegBuffer 缓存指针
* @param usAddress 起始地址
* @param usNRegs 寄存器个数
* @return eMBErrorCode 返回错误码
*/
eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
uint16_t i = 0,RegIndex = usAddress - REG_INPUT_REGISTER_START-1;
if ((usAddress >= REG_INPUT_REGISTER_START)&&(usAddress + usNRegs <= REG_INPUT_REGISTER_START + REG_INPUT_REGISTER_SIZE+1))
{
for(i=0;i<usNRegs;i++)
{
pucRegBuffer[i*2] = (UCHAR)(Input_Data[RegIndex+i]>>8);
pucRegBuffer[i*2+1] = (UCHAR)Input_Data[RegIndex+i];
}
}
else
{
return MB_ENOREG;
}
return MB_ENOERR;
}
/**********************printf重定向****************************/
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
x = x;
}
int fputc(int ch, FILE *f)
{
return ch;
}
printf リダイレクトを追加する必要があります ( printf リダイレクトに関する記事)。追加しないとプログラムがフリーズします。理由はわかりません。知っている人はコメントしてください。ありがとうございます。
User_modbus_TCP.hファイル
#ifndef __User_modbbus_TCP_H__
#define __User_modbbus_TCP_H__
#include "main.h"
#define MODBUS_TCP_PORT 4002
extern void ModbusTCPInit(void);
extern void ModbusTCPMain(void);
#endif
ステップ 7: main 関数で ModbusTCP を呼び出す
メイン関数の初期化ではModbusTCPInit() を呼び出し、
実行中のメイン関数ではModbusTCPMain() を呼び出します。
5. FreeModbus ソース コードの移植 ----テスト検証
Modbus Poll ソフトウェアを使用して、 ModbusTCP機能をテストします。
Modbus ポーリング ソフトウェア ----ダウンロードしてインストールする
ステップ 1: Modbus ポーリング ソフトウェアを開く
ステップ 2: 接続構成ウィンドウを開き、接続を構成します
メニューバーの「接続」→「接続...」をクリック(またはショートカットキーF3を押す)して、接続設定ウィンドウをポップアップ表示します。
[ModbusTCP/IP]を選択し、IP アドレスを構成し、ポートを選択します。その他の場合はデフォルト値を使用し、[ OK]をクリックします。
ステップ 3: ウィンドウ情報を構成する
[セットアップ] -> [読み取り/書き込み定義...] をクリックするか、ショートカット キー F8 を押します。
スレーブアドレス、ファンクションコード、スタートアドレス、レジスタ数などを設定し、OKをクリックします。