dev(UML-981): Решение некоторых проблем и документирование кода

1. Выделение разделяемой памяти учитывает количество требуемой памяти
2. Выложил собранные библиотеки pnet и osal
3. Добавил общее описание в readme.md
This commit is contained in:
Vadim Sychev 2022-08-05 14:58:37 +03:00
parent b992c1c3a6
commit 3c9774a173
20 changed files with 3227 additions and 38 deletions

177
README.md
View File

@ -1,3 +1,178 @@
# profinet_io_dev
Драйвер profinet io device.
Драйвер profinet io device. Предназначен для работы в фоне и взаимодействием с Profinet контроллером с использованием ethernet.
Реализует следующий функционал:
1. Читает из json файла структуру данных Profinet,
2. Конфигурирует и запускает циклический обмен по Profinet,
3. Принимает циклические данные от контроллера Profinet и записывает их в разделяемую память.
4. Берет циклические данные из разделяемой памяти и передает их контроллеру Profinet
Использует следующие библиотеки:
1. pnet
2. boost (1.74)
## Структура проекта:
* ***debug_support*** - содержит скрипт для запуска отладки с правами суперпользователя и обновления gdb (нужно для Ubuntu 22.04, там есть баг с gdb - он падает при переключении на другой поток в отлажеваемом ПО),
* ***libs*** - собранные библиотеки ***pnet*** и используемая ей ***osal***,
* ***profinet_stack*** - исходники библиотеки pnet,
* ***profinet_test*** - тестовые проекты. Наиболее интересен проект в папке ***sample_app_echo*** реализует функционал тестового модуля ECHO через взаимодействие с profinet_io_dev и подключение к разделяемой памяти.
* ***src*** - исходный код приложения profinet_io_dev.
## Общее описание данных Profinet
Терминалогия принятая в Profinet:
* Выходные данные - данные от контроллера к устройству,
* Входные данные - данные от устройства к контроллеру.
Профинет предоставляет абстракцию типичной системы управления на базе ПЛК, который имеет универсальные слоты в которые подключаются специализированные модули, например CAN, RS-422, аналоговых входов и т.д. В терминах Profinet все тоже самое: есть контроллер у которого есть слоты в которые он при установлении связи подключает модули устройства.
На стороне контроллера имеется один или несколько слотов(slots), каждый из которых имеет один или несколько подслотов(subslots).
На стороне устройства имеется один или несколько модулей(modules), каждый из которых имеет один или несколько подмодулей(submodules).
К каждому слоту на стороне контроллера может быть поключен модуль со стороны устройства.
К каждому подслоту на стороне контроллера может быть подключен подмодуль со стороны устройства.
Каждый подмодуль может включать:
* Выходные циклические данные (от контроллера к устройству)
* Входные циклические данные (от устройства к контроллеру)
* один или больше параметров.
Могут быть подмодули не содержащие данных вообще.
Параметры подмодуля отличаются от циклических данных тем, что записываются контроллером при установлении соединения, перед началом циклического обмена и больше не меняются. Их может изменить только пользователь, предварительно разорвав связь (по крайней мере в CodeSys так).
Параметры могут использоваться устройством для расчета входных данных для контроллера.
Описание конфигурации данных Profinet определяется специальным ***gsdml*** файлом, который на самом деле является ***xml*** файлом содержимое которого описано в спецификации Profinet. Этот файл нужен для всяких СКАДА систем при конфигурировании контроллера Profinet, загружаешь в СКАДА этот файл и в нем появляются Profinet устройство описанное в этом файле, а так-же все его модули, подмодули и параметры.
Идентификация модулей и подмодулей осуществляется с помощью 32-х битного идентификатора.
Идентификация параметра осуществляется с помощью 32-х битного индекса.
Установление связи выглядит следующим образом:
1. Мастер запрашивает соединение,
2. Дает команду устройству: модуль с идентификатором A подключить к слоту с номером B.
3. Дает команду устройству: подмодуль с идентификатором A входящий в состав модуля с идентификатором B подключить к подслоту с номером C слота с номером D.
4. Записывает параметры подключенного подмодуля.
5. Начинается обмен циклическими данными.
**Все данные в Profinet пишутся в порядке big endian.**
## Циклический обмен
Циклический обмен инициируется устройством, т.е. устройство должно с каким-либо периодом отправлять данные контроллеру.
## Файл конфигурации *program_configure.json*
Описывает общие настройки ПО.
***ProfinetSettings:***
* *ticks_us* - период таймера в мкс который вызывает pnet и реализует логику обмена.
* *cyclic_ms* - период обмена циклическими данными.
* *eth_dev_name* - наименование устройства ethernet в Linux через которое будет идти обмен по Profinet.
* *profinet_device_config* - путь к json файлу конфигурации устройства, который описывает структуру модулей и подмодулей.
## Файл конфигурации устройства Profinet
Описывает структуру модулей и подмодулей. В качетсве примера можно рассмотреть описание тестового модуля ECHO: profinet_device_configure.json.
***ProfinetDeviceSettings***
Содержить часть параметров структуры ***pnet_cfg*** из ***pnet_api.h***. Это стандартные параметры Profinet которые указаны и описаны в спецификации. Например имя станции или имя устройства.
***ProfinetDeviceModulesSettings***
Описание структуры данных устройства.
* ***modules***
* *id* - идентификатор модуля в шеснадцатиричном или десятичном виде. Шеснадцатиричный вид обозначется как в С через 0х.
* *name* - имя модуля, в обмене не используется, нужно для удобства.
* ***submodules*** - описание подмодулей:
* *id* - идентификатор подмодуля,
* *name* - имя подмодуля. В обмене не используется, нужно для удобства.
* *cyc_data_dir* - направление циклических данных. Может принимать значения:
* PNET_DIR_NO_IO - нет циклических данных,
* PNET_DIR_IO - входные и выходные циклические данные,
* PNET_DIR_INPUT - только входные циклические данные,
* PNET_DIR_OUTPUT - только выходные циклические данные.
* *cyc_inp_data_size* - размер входных циклических данных в байтах,
* *cyc_out_data_size* - размер выходных циклических данных в байтах,
* ***parameters*** - описание параметров:
* *index* - индекс параметра,
* *name* - наименование параметра. В обмене не используется, нужно для удобства.
* *data_size* - размер данных параметра в байтах.
## Межпроцессное взаимодействие
При запуске ПО выделяет блок разделяемой памяти с наименованием ***"profinet_shared_data"***.
Структура описана в ***profinet_data_map.hpp***.
API для работы с памятью приведено в файле: ***profinet_shared_data_client.hpp***
### Пример подключения к памяти:
```C++
ProfinetSharedDataClient shared_data;
ProfinetData_Map * p_profinet_data = shared_data.Connect("profinet_shared_data");
/// Берем указатель на данные подмодуля в разделяемой памяти
auto echo_submod_ptr = shared_data.getSubmodule(ECHO_MODULE_ID, ECHO_SUBMOD_ID);
/// Берем указатель на данные параметра подмодуля в разделяемолй памяти
auto echo_param_gain_ptr = shared_data.getSubmoduleParameter(ECHO_MODULE_ID, ECHO_SUBMOD_ID, ECHO_PARAMETER_GAIN_IDX);
```
### Пример ожидания наступления события
В структуре `ProfinetData_Map` есть разделяемые флаги `Events` типа `SharedFlags` которые могут быть использованы для ожидания следующих событий:
* `EVENT_CONNECTION_ESTABLISHED` - соединение установлено
* `EVENT_NEW_CYCLIC_DATA` - получены новые циклические данные. Не указывает на то, что данные изменились, просто по профинету была получена новая порция цикличесиких данных от контроллера.
* `EVENT_NEW_PARAM_DATA` - контроллером была осуществлена запись данных параметров какого-либо подмодуля.
* `EVENT_CONNECTION_ABORT` - соединение с контроллером разорвано.
Пример ожидания установления соединения или приема новых циклических данных:
```C++
uint32_t events = p_profinet_data->Events.wait_flags(EVENT_CONNECTION_ESTABLISHED | EVENT_NEW_CYCLIC_DATA);
```
`wait_flags` - блокирует поток до момента установления флага.
Когда флаг установлен, его нужно сбросить:
```C++
p_profinet_data->Events.clear_flags(EVENT_NEW_CYCLIC_DATA);
```
### Пример чтения данных параметров подмодуля
```C++
uint32_t Echo_Gain = 0;
echo_param_gain_ptr->data.Read(0, (uint8_t*)&Echo_Gain, echo_param_gain_ptr->length);
/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_Gain);
```
### Пример чтения и записи циклических данных подмодуля
```C++
///Читаем данные от ПЛК
echo_submod_ptr->out_data.Read(0, Echo_outCycData.mem, echo_submod_ptr->cyc_outdata_len);
/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_outCycData.data.data_i);
endian_convert_32((uint8_t*)&Echo_outCycData.data.data_f);
///Подготавливаем данные для ПЛК
Echo_inpCycData.data.data_i = Echo_Gain * Echo_outCycData.data.data_i;
Echo_inpCycData.data.data_f = Echo_Gain * Echo_outCycData.data.data_f;
///Конвертируем в биг эндиан
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_i);
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_f);
///Отправляем данные для ПЛК
echo_submod_ptr->inp_data.Write(0, Echo_inpCycData.mem, echo_submod_ptr->cyc_indata_len);
```

View File

@ -1 +1 @@
sudo /usr/bin/gdb "$@"
sudo /usr/local/bin/gdb "$@"

131
libs/include/osal.h Normal file
View File

@ -0,0 +1,131 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef OSAL_H
#define OSAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "sys/osal_sys.h"
#include "sys/osal_cc.h"
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef BIT
#define BIT(n) (1U << (n))
#endif
#ifndef NELEMENTS
#define NELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))
#endif
#ifndef OS_WAIT_FOREVER
#define OS_WAIT_FOREVER 0xFFFFFFFF
#endif
#ifndef OS_MAIN
#define OS_MAIN int main
#endif
#ifndef OS_MUTEX
typedef void os_mutex_t;
#endif
#ifndef OS_SEM
typedef void os_sem_t;
#endif
#ifndef OS_THREAD
typedef void os_thread_t;
#endif
#ifndef OS_EVENT
typedef void os_event_t;
#endif
#ifndef OS_MBOX
typedef void os_mbox_t;
#endif
#ifndef OS_TIMER
typedef void os_timer_t;
#endif
void * os_malloc (size_t size);
void os_free (void * ptr);
void os_usleep (uint32_t us);
uint32_t os_get_current_time_us (void);
os_thread_t * os_thread_create (
const char * name,
uint32_t priority,
size_t stacksize,
void (*entry) (void * arg),
void * arg);
os_mutex_t * os_mutex_create (void);
void os_mutex_lock (os_mutex_t * mutex);
void os_mutex_unlock (os_mutex_t * mutex);
void os_mutex_destroy (os_mutex_t * mutex);
os_sem_t * os_sem_create (size_t count);
bool os_sem_wait (os_sem_t * sem, uint32_t time);
void os_sem_signal (os_sem_t * sem);
void os_sem_destroy (os_sem_t * sem);
os_event_t * os_event_create (void);
bool os_event_wait (
os_event_t * event,
uint32_t mask,
uint32_t * value,
uint32_t time);
void os_event_set (os_event_t * event, uint32_t value);
void os_event_clr (os_event_t * event, uint32_t value);
void os_event_destroy (os_event_t * event);
os_mbox_t * os_mbox_create (size_t size);
bool os_mbox_fetch (os_mbox_t * mbox, void ** msg, uint32_t time);
bool os_mbox_post (os_mbox_t * mbox, void * msg, uint32_t time);
void os_mbox_destroy (os_mbox_t * mbox);
os_timer_t * os_timer_create (
uint32_t us,
void (*fn) (os_timer_t * timer, void * arg),
void * arg,
bool oneshot);
void os_timer_set (os_timer_t * timer, uint32_t us);
void os_timer_start (os_timer_t * timer);
void os_timer_stop (os_timer_t * timer);
void os_timer_destroy (os_timer_t * timer);
#ifdef __cplusplus
}
#endif
#endif /* OSAL_H */

78
libs/include/osal_log.h Normal file
View File

@ -0,0 +1,78 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef OSAL_LOG_H
#define OSAL_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
#include "osal.h"
/* Log levels */
#define LOG_LEVEL_DEBUG 0x00
#define LOG_LEVEL_INFO 0x01
#define LOG_LEVEL_WARNING 0x02
#define LOG_LEVEL_ERROR 0x03
#define LOG_LEVEL_FATAL 0x04
#define LOG_LEVEL_MASK 0x07
#define LOG_LEVEL_GET(t) (t & LOG_LEVEL_MASK)
/* Log states */
#define LOG_STATE_ON 0x80
#define LOG_STATE_OFF 0x00
#define LOG_ENABLED(type) \
((LOG_LEVEL_GET (type) >= LOG_LEVEL) && (type & LOG_STATE_ON))
/** Log a message if it is enabled */
#define LOG(type, ...) \
do \
{ \
if (LOG_ENABLED (type)) \
{ \
os_log (type, __VA_ARGS__); \
} \
} while (0)
/** Log debug messages */
#define LOG_DEBUG(type, ...) LOG ((LOG_LEVEL_DEBUG | type), __VA_ARGS__)
/** Log informational messages */
#define LOG_INFO(type, ...) LOG ((LOG_LEVEL_INFO | type), __VA_ARGS__)
/** Log warning messages */
#define LOG_WARNING(type, ...) LOG ((LOG_LEVEL_WARNING | type), __VA_ARGS__)
/** Log error messages */
#define LOG_ERROR(type, ...) LOG ((LOG_LEVEL_ERROR | type), __VA_ARGS__)
/** Log fatal messages */
#define LOG_FATAL(type, ...) LOG ((LOG_LEVEL_FATAL | type), __VA_ARGS__)
#define LOG_DEBUG_ENABLED(type) LOG_ENABLED (LOG_LEVEL_DEBUG | type)
#define LOG_INFO_ENABLED(type) LOG_ENABLED (LOG_LEVEL_INFO | type)
#define LOG_WARNING_ENABLED(type) LOG_ENABLED (LOG_LEVEL_WARNING | type)
#define LOG_ERROR_ENABLED(type) LOG_ENABLED (LOG_LEVEL_ERROR | type)
#define LOG_FATAL_ENABLED(type) LOG_ENABLED (LOG_LEVEL_FATAL | type)
void os_log (uint8_t type, const char * fmt, ...) CC_FORMAT (2, 3);
#ifdef __cplusplus
}
#endif
#endif /* OSAL_LOG_H */

View File

@ -0,0 +1,54 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2021 rt-labs AB, Sweden.
*
* This software is dual-licensed under GPLv3 and a commercial
* license. See the file LICENSE.md distributed with this software for
* full license information.
********************************************************************/
/**
* @file
* @brief PNAL-specific configuration
*
* This file contains definitions of configuration settings for the
* PNAL layer.
*/
#ifndef PNAL_CONFIG_H
#define PNAL_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/**
* Thread priority and stack size
*/
typedef struct pnal_thread_cfg
{
uint32_t prio;
size_t stack_size;
} pnal_thread_cfg_t;
typedef struct pnal_cfg
{
pnal_thread_cfg_t snmp_thread;
pnal_thread_cfg_t eth_recv_thread;
pnal_thread_cfg_t bg_worker_thread;
} pnal_cfg_t;
#ifdef __cplusplus
}
#endif
#endif /* PNAL_CONFIG_H */

2257
libs/include/pnet_api.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
#ifndef PNET_EXPORT_H
#define PNET_EXPORT_H
#ifdef PNET_STATIC_DEFINE
# define PNET_EXPORT
# define PNET_NO_EXPORT
#else
# ifndef PNET_EXPORT
# ifdef profinet_EXPORTS
/* We are building this library */
# define PNET_EXPORT
# else
/* We are using this library */
# define PNET_EXPORT
# endif
# endif
# ifndef PNET_NO_EXPORT
# define PNET_NO_EXPORT
# endif
#endif
#ifndef PNET_DEPRECATED
# define PNET_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef PNET_DEPRECATED_EXPORT
# define PNET_DEPRECATED_EXPORT PNET_EXPORT PNET_DEPRECATED
#endif
#ifndef PNET_DEPRECATED_NO_EXPORT
# define PNET_DEPRECATED_NO_EXPORT PNET_NO_EXPORT PNET_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef PNET_NO_DEPRECATED
# define PNET_NO_DEPRECATED
# endif
#endif
#endif /* PNET_EXPORT_H */

View File

@ -0,0 +1,33 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2018 rt-labs AB, Sweden.
*
* This software is dual-licensed under GPLv3 and a commercial
* license. See the file LICENSE.md distributed with this software for
* full license information.
********************************************************************/
#ifndef PNET_OPTIONS_H
#define PNET_OPTIONS_H
#if !defined (PNET_MAX_PHYSICAL_PORTS)
/** Max number of physical ports */
#define PNET_MAX_PHYSICAL_PORTS 1
#endif
#if !defined (PNET_MAX_DIRECTORYPATH_SIZE)
/** Max directory path size, including termination */
#define PNET_MAX_DIRECTORYPATH_SIZE 240
#endif
#if !defined (PNET_OPTION_DRIVER_ENABLE)
#define PNET_OPTION_DRIVER_ENABLE 0
#endif
#endif /* PNET_OPTIONS_H */

View File

@ -0,0 +1,41 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2018 rt-labs AB, Sweden.
*
* This software is dual-licensed under GPLv3 and a commercial
* license. See the file LICENSE.md distributed with this software for
* full license information.
********************************************************************/
#ifndef PNET_VERSION_H
#define PNET_VERSION_H
/* #undef PROFINET_GIT_REVISION */
#if !defined(PNET_VERSION_BUILD) && defined(PROFINET_GIT_REVISION)
#define PNET_VERSION_BUILD PROFINET_GIT_REVISION
#endif
/* clang-format-off */
#define PNET_VERSION_MAJOR 0
#define PNET_VERSION_MINOR 2
#define PNET_VERSION_PATCH 0
#if defined(PNET_VERSION_BUILD)
#define PNET_VERSION \
"0.2.0+" PNET_VERSION_BUILD
#else
#define PNET_VERSION \
"0.2.0"
#endif
/* clang-format-on */
#endif /* PNET_VERSION_H */

View File

@ -0,0 +1,98 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef CC_H
#define CC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <assert.h>
#if defined(__clang__)
#if !defined(CLANG_ANALYZER_NORETURN)
#if __has_feature(attribute_analyzer_noreturn)
#define CLANG_ANALYZER_NORETURN __attribute__ ((analyzer_noreturn))
#else
#define CLANG_ANALYZER_NORETURN
#endif
#endif
#else
#define CLANG_ANALYZER_NORETURN
#endif
static inline void cc_assert (int exp) CLANG_ANALYZER_NORETURN
{
assert (exp); // LCOV_EXCL_LINE
}
#define CC_PACKED_BEGIN
#define CC_PACKED_END
#define CC_PACKED __attribute__ ((packed))
#define CC_FORMAT(str, arg) __attribute__ ((format (printf, str, arg)))
#if BYTE_ORDER == LITTLE_ENDIAN
#define CC_TO_LE16(x) ((uint16_t)(x))
#define CC_TO_LE32(x) ((uint32_t)(x))
#define CC_TO_LE64(x) ((uint64_t)(x))
#define CC_FROM_LE16(x) ((uint16_t)(x))
#define CC_FROM_LE32(x) ((uint32_t)(x))
#define CC_FROM_LE64(x) ((uint64_t)(x))
#define CC_TO_BE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_TO_BE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_TO_BE64(x) ((uint64_t)__builtin_bswap64 (x))
#define CC_FROM_BE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_FROM_BE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_FROM_BE64(x) ((uint64_t)__builtin_bswap64 (x))
#else
#define CC_TO_LE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_TO_LE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_TO_LE64(x) ((uint64_t)__builtin_bswap64 (x))
#define CC_FROM_LE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_FROM_LE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_FROM_LE64(x) ((uint64_t)__builtin_bswap64 (x))
#define CC_TO_BE16(x) ((uint16_t)(x))
#define CC_TO_BE32(x) ((uint32_t)(x))
#define CC_TO_BE64(x) ((uint64_t)(x))
#define CC_FROM_BE16(x) ((uint16_t)(x))
#define CC_FROM_BE32(x) ((uint32_t)(x))
#define CC_FROM_BE64(x) ((uint64_t)(x))
#endif
#define CC_ATOMIC_GET8(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_GET16(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_GET32(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_GET64(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET8(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET16(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET32(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET64(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ASSERT(exp) cc_assert (exp)
#ifdef __cplusplus
#define CC_STATIC_ASSERT(exp) static_assert (exp, "")
#else
#define CC_STATIC_ASSERT(exp) _Static_assert(exp, "")
#endif
#ifdef __cplusplus
}
#endif
#endif /* CC_H */

View File

@ -0,0 +1,77 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef OSAL_SYS_H
#define OSAL_SYS_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#include <time.h>
#define OS_THREAD
#define OS_MUTEX
#define OS_SEM
#define OS_EVENT
#define OS_MBOX
#define OS_TIMER
typedef pthread_t os_thread_t;
typedef pthread_mutex_t os_mutex_t;
typedef struct os_sem
{
pthread_cond_t cond;
pthread_mutex_t mutex;
size_t count;
} os_sem_t;
typedef struct os_event
{
pthread_cond_t cond;
pthread_mutex_t mutex;
uint32_t flags;
} os_event_t;
typedef struct os_mbox
{
pthread_cond_t cond;
pthread_mutex_t mutex;
size_t r;
size_t w;
size_t count;
size_t size;
void * msg[];
} os_mbox_t;
typedef struct os_timer
{
timer_t timerid;
os_thread_t * thread;
pid_t thread_id;
bool exit;
void (*fn) (struct os_timer *, void * arg);
void * arg;
uint32_t us;
bool oneshot;
} os_timer_t;
#ifdef __cplusplus
}
#endif
#endif /* OSAL_SYS_H */

BIN
libs/lib/x86_64/libosal.a Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
#include "profinet_shared_data_client.hpp"
#include <iostream>
#include <thread>
#define ECHO_MODULE_ID 0x00000040 /// Идентификатор тестового модуля
#define ECHO_SUBMOD_ID 0x00000140 /// Идентификатор тестового подмодуля
@ -45,32 +46,34 @@ uint32_t event_mask = ( ProfinetEvent::EVENT_CONNECTION_ESTABLISHED |
ProfinetEvent::EVENT_NEW_PARAM_DATA |
ProfinetEvent::EVENT_CONNECTION_ABORT );
int main(int argc, char * argv[])
{
p_profinet_data = shared_data.Connect("profinet_shared_data");
auto& echo_module = p_profinet_data->p_mods[0];
if (echo_module.id != ECHO_MODULE_ID)
if (p_profinet_data == nullptr)
{
return 0;
}
auto& echo_submod = echo_module.p_submods[0];
/// Берем указатель на данные подмодуля в разделяемой памяти
auto echo_submod_ptr = shared_data.getSubmodule(ECHO_MODULE_ID, ECHO_SUBMOD_ID);
if (echo_submod.id != ECHO_SUBMOD_ID)
if (echo_submod_ptr->id != ECHO_SUBMOD_ID)
{
return 0;
}
/// Берем указатель на данные параметра подмодуля в разделяемолй памяти
auto echo_param_gain_ptr = shared_data.getSubmoduleParameter(ECHO_MODULE_ID, ECHO_SUBMOD_ID, ECHO_PARAMETER_GAIN_IDX);
if (echo_param_gain_ptr->index != ECHO_PARAMETER_GAIN_IDX)
{
return 0;
}
auto& echo_param_gain = echo_submod.p_params[0];
if (echo_param_gain.index != ECHO_PARAMETER_GAIN_IDX)
{
return 0;
}
std::cout << "Strarting..." << std::endl;
for(;;)
{
uint32_t events = p_profinet_data->Events.wait_flags(event_mask);
@ -84,15 +87,31 @@ int main(int argc, char * argv[])
if (events & ProfinetEvent::EVENT_NEW_CYCLIC_DATA)
{
std::cout << "New cyc data" << std::endl;
static uint32_t value_i = 0;
static float value_f = 0.0;
p_profinet_data->Events.clear_flags(ProfinetEvent::EVENT_NEW_CYCLIC_DATA);
///Читаем данные от ПЛК
echo_submod.out_data.Read(0, Echo_outCycData.mem, echo_submod.cyc_outdata_len);
echo_submod_ptr->out_data.Read(0, Echo_outCycData.mem, echo_submod_ptr->cyc_outdata_len);
/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_outCycData.data.data_i);
endian_convert_32((uint8_t*)&Echo_outCycData.data.data_f);
if (value_i != Echo_outCycData.data.data_i)
{
value_i = Echo_outCycData.data.data_i;
std::cout << "New data_i from PLC: " << value_i << std::endl;
}
if (value_f != Echo_outCycData.data.data_f)
{
value_f = Echo_outCycData.data.data_f;
std::cout << "New data_f from PLC: " << value_f << std::endl;
}
///Подготавливаем данные для ПЛК
Echo_inpCycData.data.data_i = Echo_Gain * Echo_outCycData.data.data_i;
Echo_inpCycData.data.data_f = Echo_Gain * Echo_outCycData.data.data_f;
@ -100,8 +119,7 @@ int main(int argc, char * argv[])
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_i);
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_f);
///Отправляем данные для ПЛК
echo_submod.inp_data.Write(0, Echo_inpCycData.mem, echo_submod.cyc_indata_len);
echo_submod_ptr->inp_data.Write(0, Echo_inpCycData.mem, echo_submod_ptr->cyc_indata_len);
}
if (events & ProfinetEvent::EVENT_NEW_PARAM_DATA)
@ -110,10 +128,16 @@ int main(int argc, char * argv[])
std::cout << "Event: New parameter data: " << std::endl;
//Читаем параметр записанный ПЛК при установлении связи
echo_param_gain.data.Read(0, (uint8_t*)&Echo_Gain, echo_param_gain.length);
echo_param_gain_ptr->data.Read(0, (uint8_t*)&Echo_Gain, echo_param_gain_ptr->length);
/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_Gain);
std::cout << "Echo_Gain = " << std::to_string(Echo_Gain) << endl;
}
if (events & ProfinetEvent::EVENT_CONNECTION_ABORT)
{
p_profinet_data->Events.clear_flags(ProfinetEvent::EVENT_CONNECTION_ABORT);
std::cout << "Connection Aborted" << std::endl;
}
}
}

View File

@ -1,14 +1,3 @@
#********************************************************************
# _ _ _
# _ __ | |_ _ | | __ _ | |__ ___
# | '__|| __|(_)| | / _` || '_ \ / __|
# | | | |_ _ | || (_| || |_) |\__ \
# |_| \__|(_)|_| \__,_||_.__/ |___/
#
# http://www.rt-labs.com
# Copyright 2017 rt-labs AB, Sweden.
# See LICENSE file in the project root for full license information.
#*******************************************************************/
cmake_minimum_required (VERSION 3.0)
project (PN_DEV_TEST VERSION 0.0.1)
@ -16,6 +5,7 @@ project (PN_DEV_TEST VERSION 0.0.1)
set(CMAKE_CXX_STANDARD 20)
set(LIBS_INSTALL_PATH ../libs)
set(PNET_PATH ../profinet_stack/p-net)
set(TARGET_NAME profinet_io_dev)
include(./utils/utils.cmake)
include(./profinet/profinet.cmake)
@ -27,10 +17,24 @@ include (./shared_data/shared_data.cmake)
set(SRC_FILES ${SRC_FILES} ./app.cpp)
# Копирование заглушки скрипта установки параметров сети.
# Если не копировать, то в недрах pnet после fork вызовется exit и
# вызовутся деструкторы объектов, что может привести к непредвиденным последствиям.
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/set_network_parameters
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_executable(pn_dev_test ./main.cpp ${SRC_FILES})
#Копирование файла конфигурации
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/program_configure.json
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(pn_dev_test PRIVATE
#Копирование тестового файла конфигурации устройства Profinet
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/profinet_device_configure.json
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_executable(${TARGET_NAME} ./main.cpp ${SRC_FILES})
target_include_directories(${TARGET_NAME} PRIVATE
./
${INC_DIRS}
${LIBS_INSTALL_PATH}/include
@ -40,5 +44,6 @@ target_include_directories(pn_dev_test PRIVATE
${PNET_PATH}/src/ports/linux
)
target_link_directories(pn_dev_test PUBLIC ${LIBS_INSTALL_PATH}/lib)
target_link_libraries (pn_dev_test PUBLIC profinet osal ${Boost_LIBRARIES})
target_link_directories(${TARGET_NAME} PUBLIC ${LIBS_INSTALL_PATH}/lib/x86_64)
target_link_libraries (${TARGET_NAME} PUBLIC profinet osal ${Boost_LIBRARIES})

View File

@ -157,12 +157,70 @@ static uint8_t programconf_dataDirConvert(string dir_str)
return out;
}
/**
* @brief Считаем общий размер данных требуемый всеми подмодулями: их циклическими данными и параметрами
* Пробегает по файлу конфигурации и считает размер требуемой памяти.
* @param j
* @param memory_size
* @return true
* @return false
*/
static bool programconf_calc_memory_size(json& j, uint32_t& memory_size)
{
if (!j.contains("ProfinetDeviceModulesSettings"))
return false;
auto& modules_sett = j["ProfinetDeviceModulesSettings"]["modules"];
memory_size = sizeof(ProfinetData_Map) + modules_sett.size() * sizeof(ProfinetData_Module);
for (auto& mod_set : modules_sett)
{
auto& submodules_sett = mod_set["submodules"];
memory_size += submodules_sett.size() * sizeof(ProfinetData_Submodule);
for (auto& submod_sett : submodules_sett)
{
uint16_t in_data_len = static_cast<uint16_t>(submod_sett["cyc_inp_data_size"].get<int>());
uint16_t out_data_len = static_cast<uint16_t>(submod_sett["cyc_out_data_size"].get<int>());
memory_size+= in_data_len + out_data_len;
///Параметры подмодуля
auto& params_sett = submod_sett["parameters"];
memory_size += params_sett.size() * sizeof(ProfinetData_Parameter);
/// Записываем параметры в подмодуль
for (auto& parm_sett : params_sett)
{
uint16_t data_size = static_cast<uint16_t>(parm_sett["data_size"].get<int>());
memory_size += data_size;
}
}
}
return true;
}
static bool programconf_getProfinetModulesSettings(json& j, ProfinetData_Map * p_data_map, ProfinetSharedData& shared_data)
{
if (!j.contains("ProfinetDeviceModulesSettings"))
return false;
uint32_t required_mem_size;
if (!programconf_calc_memory_size(j, required_mem_size))
return false;
/// Проверяем достаточно ли у нас выделено памяти под текущую конфигурацию
/// Если недостаточно, то выделяем недостающее.
shared_data.checkMemorySize(required_mem_size);
/// Далее идет выделение
auto& modules_sett = j["ProfinetDeviceModulesSettings"]["modules"];
///Узнаем количество модулей
uint32_t mudules_number = modules_sett.size();
@ -297,6 +355,8 @@ static bool programconf_getProfinetModulesSettings(json& j, ProfinetData_Map * p
return true;
}
bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDeviceSettings& pn_sett,
ProfinetData_Map * p_data_map, ProfinetSharedData& shared_data)
{
@ -320,3 +380,4 @@ bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDevice
return true;
}

View File

@ -38,7 +38,7 @@ ProfinetData_Map * ProfinetSharedData::Create(std::string mem_name)
{
/// Если память уже выделена, удаляем
shared_memory_object::remove(mem_name.c_str());
/// Для начала выделяем 65к байт. Потом вызывается функция checkMemorySize и размер моджет быть увеличен
shmem_ = managed_shared_memory{create_only, mem_name.c_str(), 65536};
mem_name_ = mem_name;
///Создаем данные в разделяемой памяти
@ -46,3 +46,20 @@ ProfinetData_Map * ProfinetSharedData::Create(std::string mem_name)
return p_profinet_data_;
}
void ProfinetSharedData::checkMemorySize(uint32_t required_mem_size)
{
uint32_t free_size = shmem_.get_size();
if (required_mem_size < free_size)
return;
/// Увеличиваем память
managed_shared_memory::grow(mem_name_.c_str(), required_mem_size - free_size);
/// Заново открываем память(может измениться указатель)
shmem_ = managed_shared_memory{open_only, mem_name_.c_str()};
/// Заново находим или создаем структуру ProfinetData_Map
p_profinet_data_ = shmem_.find_or_construct<ProfinetData_Map>("ProfinetData_Map")();
}

View File

@ -13,7 +13,8 @@ class ProfinetSharedData {
public:
~ProfinetSharedData() {
///Удаляем память
//boost::interprocess::shared_memory_object::remove(mem_name_.c_str());
boost::interprocess::shared_memory_object::remove(mem_name_.c_str());
//std::cout << "Destruct: ProfinetSharedData: Shared data deleted" << std::endl;
}
/**
* @brief Выделяыет раздеяемую память под структуру ProfinetData_Map
@ -29,6 +30,15 @@ public:
return shmem_.construct<T>(boost::interprocess::anonymous_instance)[nbr]();
}
/**
* @brief Проверяет, что размер свободной памяти не меньше чем значение required_mem_size
* Если меньше, то увеличиват память.
*
* @param required_mem_size
* @return uint32_t
*/
void checkMemorySize(uint32_t required_mem_size);
ProfinetData_Map * p_profinet_data_;
private:

View File

@ -4,6 +4,8 @@
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
#include <map>
#include <memory>
class ProfinetSharedDataClient {
public:
@ -22,6 +24,8 @@ public:
{
auto mem = shmem_.find<ProfinetData_Map>("ProfinetData_Map");
out = mem.first;
///Упорядочиваем память для удобства обращения по индентификатору Profinet
collected_data = collect(out);
}
catch(const std::exception& e)
{
@ -34,10 +38,76 @@ public:
std::cerr << e.what() << '\n';
return out;
}
return out;
}
/**
* @brief Возвращает указатель на данные подмодуля в разделяемой памяти
*
* @param module_id идентификатор модуля
* @param submodule_id идентификатор подмодуля
* @return ProfinetData_Submodule*
*/
ProfinetData_Submodule * getSubmodule(uint32_t module_id, uint32_t submodule_id)
{
return (*collected_data)[module_id].submods[submodule_id].p_submod;
}
/**
* @brief Возвращает указатель на данные параметра подмодуля в разделяемой памяти
*
* @param module_id идентификатор модуля
* @param submodule_id идентификатор подмодуля
* @param param_index индекс параметра
* @return ProfinetData_Parameter*
*/
ProfinetData_Parameter * getSubmoduleParameter(uint32_t module_id, uint32_t submodule_id, uint32_t param_index)
{
return (*collected_data)[module_id].submods[submodule_id].params[param_index];
}
private:
struct ProfinetSharedDataSubmodule {
ProfinetData_Submodule* p_submod;
std::map<uint32_t, ProfinetData_Parameter*> params;
};
struct ProfinetSharedDataModule {
ProfinetData_Module* p_mod;
std::map<uint32_t, ProfinetSharedDataSubmodule> submods;
};
/**
* @brief Собирает данные в словарь, можно использовать для удобства обращения к данным по идентификатору
*
* @param p_data_map
* @return std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>>
*/
static std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>> collect(ProfinetData_Map * p_data_map) {
std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>> p_map(new std::map<uint32_t, ProfinetSharedDataModule>);
for (int i = 0; i < p_data_map->mods_nbr; ++i)
{
auto& mod = (*p_map)[p_data_map->p_mods[i].id];
mod.p_mod = &p_data_map->p_mods[i];
for (int j = 0; j < p_data_map->p_mods[i].submods_nbr; ++j)
{
auto& submod = mod.submods[p_data_map->p_mods[i].p_submods[j].id];
submod.p_submod = &p_data_map->p_mods[i].p_submods[j];
for (int k = 0; k < p_data_map->p_mods[i].p_submods[j].params_nbr; ++k)
{
submod.params[p_data_map->p_mods[i].p_submods[j].p_params[k].index] = &p_data_map->p_mods[i].p_submods[j].p_params[k];
}
}
}
return p_map;
}
private:
boost::interprocess::managed_shared_memory shmem_;
std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>> collected_data; /// Упорядоченная структура указателей на разделяемую память
};

16
src/set_network_parameters Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env sh
############################################
# Скрипт для установки параметров сети #
############################################
#
# Данный скрипт запускается стеком pnet при инициализации.
# Если файл с именем set_network_parameters будет отсутсвовать,
# то в недрах pnet после вызова fork будет вызван exit, что может привести
# к нежелательным последствиям, например вызовам деструкторов копий объектов
# родительского процесса.
#
echo "set_network_parameters: This is dummy script wich called by pnet stack. As example see pnet/src/ports/linux/set_network_parameters."
exit 0