[Etherlab-users] EL3204 Analog PDO Data Issue
Graeme Foot
Graeme.Foot at touchcut.com
Wed Nov 8 21:21:28 CET 2023
Hi David,
In cyclic_task() it looks like you are missing ecrt_domain_queue(domain1) just before ecrt_master_send().
I haven't checked for any further problems.
Regards,
Graeme.
From: Etherlab-users <etherlab-users-bounces at etherlab.org> On Behalf Of David Meehan
Sent: Thursday, November 9, 2023 7:15 AM
To: etherlab-users at etherlab.org
Subject: [Etherlab-users] EL3204 Analog PDO Data Issue
Hi all,
I'm completely new to EtherCAT, so bear with me. I'm in the process of trying to bring up an EL3204 4-channel analog input terminal. I'm running into an issue where it doesn't look like I'm getting any PDO data back. The working counter seems stuck at [0/1]. I have some code based on the dc_user example project (see code below). Our system has the following slave terminals.
// Taken from:
// > ethercat slaves
// 0 0:0 PREOP + EK1100 EtherCAT-Koppler (2A E-Bus)
// 1 0:1 PREOP + EL1809 16K. Dig. Eingang 24V, 3ms
// 2 0:2 PREOP + EL2809 16K. Dig. Ausgang 24V, 0.5A
// 3 0:3 PREOP + EL6224 (IO Link Master)
// 4 0:4 PREOP + EL7031 1K. Schrittmotor-Endstufe (24V, 1.5A)
// 5 0:5 PREOP + EL3204-0200 4K. Ana. Ein. PT1000 (RTD)
For right now I'm only interested in setting up the EL3204. I ran the "ethercat cstruct" command to get the sync manager and pdo listings for slave 5. Using those I was able to create a ec_pdo_entry_reg_t registration list containing the 4 channel values. I pass the sync info objects through ecrt_slave_config_pdos, and pass the domain registration list through ecrt_domain_reg_pdo_entry_list. Everything seems to work and the slave node goes to OP state after activating the master. I printed out the byte offset mappings and it looks like ec master was able to allocate a data buffer and assign the PDOs. Everything looks good.
But I'm still not getting any PDO data. Everything is reporting back as 0 and the WC is not incrementing. I ran a little experiment and setup some SDO request objects and tried reading back the sensor values using SDOs instead of PDOs, and it worked! The readings I'm getting back from the SDOs match the values I was seeing in TwinCAT. So I know the comms are good.
Anyway, if anyone has any suggestions, it would be appreciated.
Thanks,
Dave M
/*****************************************************************************
*
* Copyright (C) 2007-2022 Florian Pose, Ingenieurgemeinschaft IgH
*
* This file is part of the IgH EtherCAT Master.
*
* The IgH EtherCAT Master is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* The IgH EtherCAT Master is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the IgH EtherCAT Master; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* ---
*
* The license mentioned above concerns the source code only. Using the
* EtherCAT technology and brand is only permitted in compliance with the
* industrial property and similar rights of Beckhoff Automation GmbH.
*
****************************************************************************/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <sys/mman.h>
#include <malloc.h>
#include <sched.h> /* sched_setscheduler() */
#include "ecrt.h"
/****************************************************************************/
// Application parameters
#define FREQUENCY 1000
#define CLOCK_TO_USE CLOCK_MONOTONIC
#define MEASURE_TIMING
// Taken from ethercat slaves
// 0 0:0 PREOP + EK1100 EtherCAT-Koppler (2A E-Bus)
// 1 0:1 PREOP + EL1809 16K. Dig. Eingang 24V, 3ms
// 2 0:2 PREOP + EL2809 16K. Dig. Ausgang 24V, 0.5A
// 3 0:3 PREOP + EL6224 (IO Link Master)
// 4 0:4 PREOP + EL7031 1K. Schrittmotor-Endstufe (24V, 1.5A)
// 5 0:5 PREOP + EL3204-0200 4K. Ana. Ein. PT1000 (RTD)
// Taken from ethercat cstruct
/* Master 0, Slave 5, "EL3204-0200"
* Vendor ID: 0x00000002
* Product code: 0x0c843052
* Revision number: 0x001200c8
*/
ec_pdo_entry_info_t slave_5_pdo_entries[] = {
{0x6000, 0x01, 1}, /* Underrange */
{0x6000, 0x02, 1}, /* Overrange */
{0x0000, 0x00, 4}, /* Gap */
{0x6000, 0x07, 1}, /* Error */
{0x0000, 0x00, 7}, /* Gap */
{0x6000, 0x0f, 1}, /* TxPDO State */
{0x6000, 0x10, 1}, /* TxPDO Toggle */
{0x6000, 0x11, 16}, /* Value */
{0x6010, 0x01, 1}, /* Underrange */
{0x6010, 0x02, 1}, /* Overrange */
{0x0000, 0x00, 4}, /* Gap */
{0x6010, 0x07, 1}, /* Error */
{0x0000, 0x00, 7}, /* Gap */
{0x6010, 0x0f, 1}, /* TxPDO State */
{0x6010, 0x10, 1}, /* TxPDO Toggle */
{0x6010, 0x11, 16}, /* Value */
{0x6020, 0x01, 1}, /* Underrange */
{0x6020, 0x02, 1}, /* Overrange */
{0x0000, 0x00, 4}, /* Gap */
{0x6020, 0x07, 1}, /* Error */
{0x0000, 0x00, 7}, /* Gap */
{0x6020, 0x0f, 1}, /* TxPDO State */
{0x6020, 0x10, 1}, /* TxPDO Toggle */
{0x6020, 0x11, 16}, /* Value */
{0x6030, 0x01, 1}, /* Underrange */
{0x6030, 0x02, 1}, /* Overrange */
{0x0000, 0x00, 4}, /* Gap */
{0x6030, 0x07, 1}, /* Error */
{0x0000, 0x00, 7}, /* Gap */
{0x6030, 0x0f, 1}, /* TxPDO State */
{0x6030, 0x10, 1}, /* TxPDO Toggle */
{0x6030, 0x11, 16}, /* Value */
};
ec_pdo_info_t slave_5_pdos[] = {
{0x1a00, 8, slave_5_pdo_entries + 0}, /* RTD TxPDO-Map Ch.1 */
{0x1a01, 8, slave_5_pdo_entries + 8}, /* RTD TxPDO-Map Ch.2 */
{0x1a02, 8, slave_5_pdo_entries + 16}, /* RTD TxPDO-Map Ch.3 */
{0x1a03, 8, slave_5_pdo_entries + 24}, /* RTD TxPDO-Map Ch.4 */
};
ec_sync_info_t slave_5_syncs[] = {
{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
{2, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{3, EC_DIR_INPUT, 4, slave_5_pdos + 0, EC_WD_DISABLE},
{0xff}
};
// typedef struct {
// uint16_t alias;
// uint16_t position;
// uint32_t vendor_id;
// uint32_t product_code;
// uint16_t index;
// uint8_t subindex;
// unsigned int *offset;
// unsigned int *bit_position;
// } ec_pdo_entry_reg_t;
unsigned int val1Offset = 0u;
unsigned int val1bit = 0u;
unsigned int val2Offset = 0u;
unsigned int val2bit = 0u;
unsigned int val3Offset = 0u;
unsigned int val3bit = 0u;
unsigned int val4Offset = 0u;
unsigned int val4bit = 0u;
// Taken from ethercat cstruct
ec_pdo_entry_reg_t registrationList[] = {
{ 0, 5, 2, 0xc843052, 0x6000, 0x11, &val1Offset, &val1bit },
{ 0, 5, 2, 0xc843052, 0x6010, 0x11, &val2Offset, &val2bit },
{ 0, 5, 2, 0xc843052, 0x6020, 0x11, &val3Offset, &val3bit },
{ 0, 5, 2, 0xc843052, 0x6030, 0x11, &val4Offset, &val4bit }
};
/****************************************************************************/
#define NSEC_PER_SEC (1000000000L)
#define PERIOD_NS (NSEC_PER_SEC / FREQUENCY)
#define DIFF_NS(A, B) (((B).tv_sec - (A).tv_sec) * NSEC_PER_SEC + \
(B).tv_nsec - (A).tv_nsec)
#define TIMESPEC2NS(T) ((uint64_t) (T).tv_sec * NSEC_PER_SEC + (T).tv_nsec)
/****************************************************************************/
// EtherCAT
static ec_master_t *master = NULL;
static ec_master_state_t master_state = {};
static ec_domain_t *domain1 = NULL;
static ec_domain_state_t domain1_state = {};
/****************************************************************************/
// process data
static uint8_t *domain1_pd = NULL;
#define BusCouplerPos 0, 0
#define DigInSlavePos 0, 1
#define CounterSlavePos 0, 2
#define Beckhoff_EK1100 0x00000002, 0x044c2c52
#define Beckhoff_EL3204 0x00000002, 0x0c843052 // TODO Set to the EL3204
// offsets for PDO entries
static int off_dig_out;
static int off_counter_in;
static int off_counter_out;
static unsigned int counter = 0;
static unsigned int blink = 0;
static unsigned int sync_ref_counter = 0;
const struct timespec cycletime = {0, PERIOD_NS};
/****************************************************************************/
struct timespec timespec_add(struct timespec time1, struct timespec time2)
{
struct timespec result;
if ((time1.tv_nsec + time2.tv_nsec) >= NSEC_PER_SEC) {
result.tv_sec = time1.tv_sec + time2.tv_sec + 1;
result.tv_nsec = time1.tv_nsec + time2.tv_nsec - NSEC_PER_SEC;
} else {
result.tv_sec = time1.tv_sec + time2.tv_sec;
result.tv_nsec = time1.tv_nsec + time2.tv_nsec;
}
return result;
}
/****************************************************************************/
void check_domain1_state(void)
{
ec_domain_state_t ds;
ecrt_domain_state(domain1, &ds);
if (ds.working_counter != domain1_state.working_counter)
printf("Domain1: WC %u.\n", ds.working_counter);
if (ds.wc_state != domain1_state.wc_state)
printf("Domain1: State %u.\n", ds.wc_state);
domain1_state = ds;
}
/****************************************************************************/
void check_master_state(void)
{
ec_master_state_t ms;
ecrt_master_state(master, &ms);
if (ms.slaves_responding != master_state.slaves_responding)
printf("%u slave(s).\n", ms.slaves_responding);
if (ms.al_states != master_state.al_states)
printf("AL states: 0x%02X.\n", ms.al_states);
if (ms.link_up != master_state.link_up)
printf("Link is %s.\n", ms.link_up ? "up" : "down");
master_state = ms;
}
/****************************************************************************/
void cyclic_task()
{
struct timespec wakeupTime, time;
#ifdef MEASURE_TIMING
struct timespec startTime, endTime, lastStartTime = {};
uint32_t period_ns = 0, exec_ns = 0, latency_ns = 0,
latency_min_ns = 0, latency_max_ns = 0,
period_min_ns = 0, period_max_ns = 0,
exec_min_ns = 0, exec_max_ns = 0;
#endif
// get current time
clock_gettime(CLOCK_TO_USE, &wakeupTime);
while(1) {
wakeupTime = timespec_add(wakeupTime, cycletime);
clock_nanosleep(CLOCK_TO_USE, TIMER_ABSTIME, &wakeupTime, NULL);
// Write application time to master
//
// It is a good idea to use the target time (not the measured time) as
// application time, because it is more stable.
//
ecrt_master_application_time(master, TIMESPEC2NS(wakeupTime));
#ifdef MEASURE_TIMING
clock_gettime(CLOCK_TO_USE, &startTime);
latency_ns = DIFF_NS(wakeupTime, startTime);
period_ns = DIFF_NS(lastStartTime, startTime);
exec_ns = DIFF_NS(lastStartTime, endTime);
lastStartTime = startTime;
if (latency_ns > latency_max_ns) {
latency_max_ns = latency_ns;
}
if (latency_ns < latency_min_ns) {
latency_min_ns = latency_ns;
}
if (period_ns > period_max_ns) {
period_max_ns = period_ns;
}
if (period_ns < period_min_ns) {
period_min_ns = period_ns;
}
if (exec_ns > exec_max_ns) {
exec_max_ns = exec_ns;
}
if (exec_ns < exec_min_ns) {
exec_min_ns = exec_ns;
}
#endif
// receive process data
ecrt_master_receive(master);
ecrt_domain_process(domain1);
// check process data state (optional)
check_domain1_state();
if (counter) {
counter--;
} else { // do this at 1 Hz
counter = FREQUENCY;
// check for master state (optional)
check_master_state();
#ifdef MEASURE_TIMING
// output timing stats
printf("period %10u ... %10u\n",
period_min_ns, period_max_ns);
printf("exec %10u ... %10u\n",
exec_min_ns, exec_max_ns);
printf("latency %10u ... %10u\n",
latency_min_ns, latency_max_ns);
period_max_ns = 0;
period_min_ns = 0xffffffff;
exec_max_ns = 0;
exec_min_ns = 0xffffffff;
latency_max_ns = 0;
latency_min_ns = 0xffffffff;
#endif
}
// Read process data
int16_t val1 = EC_READ_S16(domain1_pd + val1Offset);
int16_t val2 = EC_READ_S16(domain1_pd + val1Offset);
int16_t val3 = EC_READ_S16(domain1_pd + val1Offset);
int16_t val4 = EC_READ_S16(domain1_pd + val1Offset);
printf("Val1: %d\nVal2: %d\nVal3: %d\nVal4: %d\n\n", val1, val2, val3, val4);
if (sync_ref_counter) {
sync_ref_counter--;
} else {
sync_ref_counter = 1; // sync every cycle
clock_gettime(CLOCK_TO_USE, &time);
ecrt_master_sync_reference_clock_to(master, TIMESPEC2NS(time));
}
ecrt_master_sync_slave_clocks(master);
ecrt_master_send(master);
#ifdef MEASURE_TIMING
clock_gettime(CLOCK_TO_USE, &endTime);
#endif
}
}
/****************************************************************************/
int main(int argc, char **argv)
{
ec_slave_config_t *sc;
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
perror("mlockall failed");
return -1;
}
master = ecrt_request_master(0);
if (!master)
return -1;
domain1 = ecrt_master_create_domain(master);
if (!domain1)
return -1;
// Create configuration for bus coupler
sc = ecrt_master_slave_config(master, BusCouplerPos, Beckhoff_EK1100);
if (!sc)
return -1;
if (!(sc = ecrt_master_slave_config(master,
DigInSlavePos, Beckhoff_EL3204))) {
fprintf(stderr, "Failed to get slave configuration.\n");
return -1;
}
if ( ecrt_slave_config_pdos(sc, 5, &slave_5_syncs[0] ) )
{
printf("Failed to set sync manager data\n");
return -1;
}
printf("Registering PDO entries...\n");
if (ecrt_domain_reg_pdo_entry_list(domain1, ®istrationList[0])) {
fprintf(stderr, "Failed to set slave pdos.\n");
return -1;
}
// configure SYNC signals for this slave
// NOTE: EL3204 uses the same assignActivate value
ecrt_slave_config_dc(sc, 0x0700, PERIOD_NS, 4400000, 0, 0);
printf("Activating master...\n");
if (ecrt_master_activate(master))
return -1;
if (!(domain1_pd = ecrt_domain_data(domain1))) {
return -1;
}
printf( "Domain Size Is %d bytes\n", ecrt_domain_size( domain1 ) );
printf( " Domain Val1 Offset Is %d\n", val1Offset );
printf( " Domain Val2 Offset Is %d\n", val2Offset );
printf( " Domain Val3 Offset Is %d\n", val3Offset );
printf( " Domain Val4 Offset Is %d\n", val4Offset );
/* Set priority */
struct sched_param param = {};
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
printf("Using priority %i.", param.sched_priority);
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler failed");
}
printf("Starting cyclic function.\n");
cyclic_task();
return 0;
}
/****************************************************************************/
This e-mail and the information, including any attachments it contains, are intended to be a confidential communication only to the person or entity to whom it is addressed and may contain information that is privileged. If the reader of this message is not the intended recipient, you are hereby notified that any dissemination, distribution or copying of this communication is strictly prohibited. If you have received this communication in error, please immediately notify the sender and destroy the original message. Thank you. Please consider the environment before printing this email.
This message has been scanned for malware by Websense. www.websense.com<http://www.websense.com/>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.etherlab.org/pipermail/etherlab-users/attachments/20231108/87958aa4/attachment-0001.htm>
More information about the Etherlab-users
mailing list