1271 lines
38 KiB
C
1271 lines
38 KiB
C
/******************************************************************************
|
|
*
|
|
* m a s t e r . c
|
|
*
|
|
* Methoden für einen EtherCAT-Master.
|
|
*
|
|
* $Id$
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "../include/ecrt.h"
|
|
#include "globals.h"
|
|
#include "master.h"
|
|
#include "slave.h"
|
|
#include "types.h"
|
|
#include "device.h"
|
|
#include "command.h"
|
|
#include "ethernet.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Konstruktor des EtherCAT-Masters.
|
|
*/
|
|
|
|
void ec_master_init(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
master->slaves = NULL;
|
|
master->device = NULL;
|
|
|
|
INIT_LIST_HEAD(&master->command_queue);
|
|
INIT_LIST_HEAD(&master->domains);
|
|
INIT_LIST_HEAD(&master->eoe_slaves);
|
|
|
|
ec_command_init(&master->simple_command);
|
|
ec_command_init(&master->watch_command);
|
|
|
|
ec_master_reset(master);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Destruktor eines EtherCAT-Masters.
|
|
|
|
Entfernt alle Kommandos aus der Liste, löscht den Zeiger
|
|
auf das Slave-Array und gibt die Prozessdaten frei.
|
|
*/
|
|
|
|
void ec_master_clear(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_master_reset(master);
|
|
|
|
if (master->device) {
|
|
ec_device_clear(master->device);
|
|
kfree(master->device);
|
|
}
|
|
|
|
ec_command_clear(&master->simple_command);
|
|
ec_command_clear(&master->watch_command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Setzt den Master zurück in den Ausgangszustand.
|
|
|
|
Bei einem "release" sollte immer diese Funktion aufgerufen werden,
|
|
da sonst Slave-Liste, Domains, etc. weiter existieren.
|
|
*/
|
|
|
|
void ec_master_reset(ec_master_t *master
|
|
/**< Zeiger auf den zurückzusetzenden Master */
|
|
)
|
|
{
|
|
unsigned int i;
|
|
ec_command_t *command, *next_c;
|
|
ec_domain_t *domain, *next_d;
|
|
ec_eoe_t *eoe, *next_eoe;
|
|
|
|
// Alle Slaves entfernen
|
|
if (master->slaves) {
|
|
for (i = 0; i < master->slave_count; i++) {
|
|
ec_slave_clear(master->slaves + i);
|
|
}
|
|
kfree(master->slaves);
|
|
master->slaves = NULL;
|
|
}
|
|
master->slave_count = 0;
|
|
|
|
// Kommando-Warteschlange leeren
|
|
list_for_each_entry_safe(command, next_c, &master->command_queue, queue) {
|
|
command->state = EC_CMD_ERROR;
|
|
list_del_init(&command->queue);
|
|
}
|
|
|
|
// Domain-Liste leeren
|
|
list_for_each_entry_safe(domain, next_d, &master->domains, list) {
|
|
list_del(&domain->list);
|
|
ec_domain_clear(domain);
|
|
kfree(domain);
|
|
}
|
|
|
|
// EOE-Liste leeren
|
|
list_for_each_entry_safe(eoe, next_eoe, &master->domains, list) {
|
|
list_del(&eoe->list);
|
|
ec_eoe_clear(eoe);
|
|
kfree(eoe);
|
|
}
|
|
|
|
master->command_index = 0;
|
|
master->debug_level = 0;
|
|
master->timeout = 500; // 500us
|
|
|
|
master->slaves_responding = 0;
|
|
master->slave_states = EC_SLAVE_STATE_UNKNOWN;
|
|
|
|
master->stats.timeouts = 0;
|
|
master->stats.delayed = 0;
|
|
master->stats.corrupted = 0;
|
|
master->stats.unmatched = 0;
|
|
master->stats.t_last = 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Stellt ein Kommando in die Warteschlange.
|
|
*/
|
|
|
|
void ec_master_queue_command(ec_master_t *master, /**< EtherCAT-Master */
|
|
ec_command_t *command /**< Kommando */
|
|
)
|
|
{
|
|
ec_command_t *queued_command;
|
|
|
|
// Ist das Kommando schon in der Warteschlange?
|
|
list_for_each_entry(queued_command, &master->command_queue, queue) {
|
|
if (queued_command == command) {
|
|
command->state = EC_CMD_QUEUED;
|
|
if (unlikely(master->debug_level))
|
|
EC_WARN("command already queued.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&command->queue, &master->command_queue);
|
|
command->state = EC_CMD_QUEUED;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet die Kommandos in der Warteschlange.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
void ec_master_send_commands(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_command_t *command;
|
|
size_t command_size;
|
|
uint8_t *frame_data, *cur_data;
|
|
void *follows_word;
|
|
cycles_t start = 0, end;
|
|
|
|
if (unlikely(master->debug_level > 0)) {
|
|
EC_DBG("ec_master_send\n");
|
|
start = get_cycles();
|
|
}
|
|
|
|
// Zeiger auf Socket-Buffer holen
|
|
frame_data = ec_device_tx_data(master->device);
|
|
cur_data = frame_data + EC_FRAME_HEADER_SIZE;
|
|
follows_word = NULL;
|
|
|
|
// Aktuellen Frame mit Kommandos füllen
|
|
list_for_each_entry(command, &master->command_queue, queue) {
|
|
if (command->state != EC_CMD_QUEUED) continue;
|
|
|
|
// Passt das aktuelle Kommando noch in den aktuellen Rahmen?
|
|
command_size = EC_COMMAND_HEADER_SIZE + command->data_size
|
|
+ EC_COMMAND_FOOTER_SIZE;
|
|
if (cur_data - frame_data + command_size > EC_MAX_FRAME_SIZE) break;
|
|
|
|
command->state = EC_CMD_SENT;
|
|
command->index = master->command_index++;
|
|
|
|
if (unlikely(master->debug_level > 0))
|
|
EC_DBG("adding command 0x%02X\n", command->index);
|
|
|
|
// Command-Following-Flag im letzten Kommando setzen
|
|
if (follows_word)
|
|
EC_WRITE_U16(follows_word, EC_READ_U16(follows_word) | 0x8000);
|
|
|
|
// EtherCAT command header
|
|
EC_WRITE_U8 (cur_data, command->type);
|
|
EC_WRITE_U8 (cur_data + 1, command->index);
|
|
EC_WRITE_U32(cur_data + 2, command->address.logical);
|
|
EC_WRITE_U16(cur_data + 6, command->data_size & 0x7FF);
|
|
EC_WRITE_U16(cur_data + 8, 0x0000);
|
|
follows_word = cur_data + 6;
|
|
cur_data += EC_COMMAND_HEADER_SIZE;
|
|
|
|
// EtherCAT command data
|
|
memcpy(cur_data, command->data, command->data_size);
|
|
cur_data += command->data_size;
|
|
|
|
// EtherCAT command footer
|
|
EC_WRITE_U16(cur_data, 0x0000); // Working counter
|
|
cur_data += EC_COMMAND_FOOTER_SIZE;
|
|
}
|
|
|
|
if (cur_data - frame_data == EC_FRAME_HEADER_SIZE) {
|
|
if (unlikely(master->debug_level > 0)) EC_DBG("nothing to send.\n");
|
|
return;
|
|
}
|
|
|
|
// EtherCAT frame header
|
|
EC_WRITE_U16(frame_data, ((cur_data - frame_data
|
|
- EC_FRAME_HEADER_SIZE) & 0x7FF) | 0x1000);
|
|
|
|
// Rahmen auffüllen
|
|
while (cur_data - frame_data < EC_MIN_FRAME_SIZE)
|
|
EC_WRITE_U8(cur_data++, 0x00);
|
|
|
|
if (unlikely(master->debug_level > 0))
|
|
EC_DBG("Frame size: %i\n", cur_data - frame_data);
|
|
|
|
// Send frame
|
|
ec_device_send(master->device, cur_data - frame_data);
|
|
|
|
if (unlikely(master->debug_level > 0)) {
|
|
end = get_cycles();
|
|
EC_DBG("ec_master_send finished in %ius.\n",
|
|
(u32) (end - start) * 1000 / cpu_khz);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Wertet einen empfangenen Rahmen aus.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
void ec_master_receive(ec_master_t *master, /**< EtherCAT-Master */
|
|
const uint8_t *frame_data, /**< Empfangene Daten */
|
|
size_t size /**< Anzahl empfangene Datenbytes */
|
|
)
|
|
{
|
|
size_t frame_size, data_size;
|
|
uint8_t command_type, command_index;
|
|
unsigned int cmd_follows, matched;
|
|
const uint8_t *cur_data;
|
|
ec_command_t *command;
|
|
|
|
if (unlikely(size < EC_FRAME_HEADER_SIZE)) {
|
|
master->stats.corrupted++;
|
|
ec_master_output_stats(master);
|
|
return;
|
|
}
|
|
|
|
cur_data = frame_data;
|
|
|
|
// Länge des gesamten Frames prüfen
|
|
frame_size = EC_READ_U16(cur_data) & 0x07FF;
|
|
cur_data += EC_FRAME_HEADER_SIZE;
|
|
|
|
if (unlikely(frame_size > size)) {
|
|
master->stats.corrupted++;
|
|
ec_master_output_stats(master);
|
|
return;
|
|
}
|
|
|
|
cmd_follows = 1;
|
|
while (cmd_follows) {
|
|
// Kommando-Header auswerten
|
|
command_type = EC_READ_U8 (cur_data);
|
|
command_index = EC_READ_U8 (cur_data + 1);
|
|
data_size = EC_READ_U16(cur_data + 6) & 0x07FF;
|
|
cmd_follows = EC_READ_U16(cur_data + 6) & 0x8000;
|
|
cur_data += EC_COMMAND_HEADER_SIZE;
|
|
|
|
if (unlikely(cur_data - frame_data
|
|
+ data_size + EC_COMMAND_FOOTER_SIZE > size)) {
|
|
master->stats.corrupted++;
|
|
ec_master_output_stats(master);
|
|
return;
|
|
}
|
|
|
|
// Suche passendes Kommando in der Liste
|
|
matched = 0;
|
|
list_for_each_entry(command, &master->command_queue, queue) {
|
|
if (command->state == EC_CMD_SENT
|
|
&& command->type == command_type
|
|
&& command->index == command_index
|
|
&& command->data_size == data_size) {
|
|
matched = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Kein passendes Kommando in der Liste gefunden
|
|
if (!matched) {
|
|
master->stats.unmatched++;
|
|
ec_master_output_stats(master);
|
|
cur_data += data_size + EC_COMMAND_FOOTER_SIZE;
|
|
continue;
|
|
}
|
|
|
|
// Empfangene Daten in Kommando-Datenspeicher kopieren
|
|
memcpy(command->data, cur_data, data_size);
|
|
cur_data += data_size;
|
|
|
|
// Working-Counter setzen
|
|
command->working_counter = EC_READ_U16(cur_data);
|
|
cur_data += EC_COMMAND_FOOTER_SIZE;
|
|
|
|
// Kommando aus der Warteschlange entfernen
|
|
command->state = EC_CMD_RECEIVED;
|
|
list_del_init(&command->queue);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet ein einzelnes Kommando und wartet auf den Empfang.
|
|
|
|
Wenn der Slave nicht antwortet, wird das Kommando
|
|
nochmals gesendet.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int ec_master_simple_io(ec_master_t *master, /**< EtherCAT-Master */
|
|
ec_command_t *command /**< Kommando */
|
|
)
|
|
{
|
|
unsigned int response_tries_left;
|
|
|
|
response_tries_left = 10;
|
|
|
|
while (1)
|
|
{
|
|
ec_master_queue_command(master, command);
|
|
ecrt_master_sync_io(master);
|
|
|
|
if (command->state == EC_CMD_RECEIVED) {
|
|
if (likely(command->working_counter))
|
|
return 0;
|
|
}
|
|
else if (command->state == EC_CMD_TIMEOUT) {
|
|
EC_ERR("Simple-IO TIMEOUT!\n");
|
|
return -1;
|
|
}
|
|
else if (command->state == EC_CMD_ERROR) {
|
|
EC_ERR("Simple-IO command error!\n");
|
|
return -1;
|
|
}
|
|
|
|
// Keine direkte Antwort. Dem Slave Zeit lassen...
|
|
udelay(100);
|
|
|
|
if (unlikely(--response_tries_left)) {
|
|
EC_ERR("No response in simple-IO!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Durchsucht den EtherCAT-Bus nach Slaves.
|
|
|
|
Erstellt ein Array mit allen Slave-Informationen die für den
|
|
weiteren Betrieb notwendig sind.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int ec_master_bus_scan(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_slave_t *slave;
|
|
ec_slave_ident_t *ident;
|
|
unsigned int i;
|
|
ec_command_t *command;
|
|
ec_eoe_t *eoe;
|
|
|
|
if (master->slaves || master->slave_count) {
|
|
EC_ERR("Slave scan already done!\n");
|
|
return -1;
|
|
}
|
|
|
|
command = &master->simple_command;
|
|
|
|
// Determine number of slaves on bus
|
|
if (ec_command_brd(command, 0x0000, 4)) return -1;
|
|
if (unlikely(ec_master_simple_io(master, command))) return -1;
|
|
master->slave_count = command->working_counter;
|
|
EC_INFO("Found %i slaves on bus.\n", master->slave_count);
|
|
|
|
if (!master->slave_count) return 0;
|
|
|
|
if (!(master->slaves = (ec_slave_t *) kmalloc(master->slave_count
|
|
* sizeof(ec_slave_t),
|
|
GFP_KERNEL))) {
|
|
EC_ERR("Could not allocate memory for slaves!\n");
|
|
return -1;
|
|
}
|
|
|
|
// Init slaves
|
|
for (i = 0; i < master->slave_count; i++) {
|
|
slave = master->slaves + i;
|
|
ec_slave_init(slave, master);
|
|
slave->ring_position = i;
|
|
slave->station_address = i + 1;
|
|
}
|
|
|
|
// For every slave in the list
|
|
for (i = 0; i < master->slave_count; i++)
|
|
{
|
|
slave = master->slaves + i;
|
|
|
|
// Write station address
|
|
if (ec_command_apwr(command, slave->ring_position,
|
|
0x0010, sizeof(uint16_t))) return -1;
|
|
EC_WRITE_U16(command->data, slave->station_address);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Writing station address failed on slave %i!\n", i);
|
|
return -1;
|
|
}
|
|
|
|
// Fetch all slave information
|
|
if (ec_slave_fetch(slave)) return -1;
|
|
|
|
// Search for identification in "database"
|
|
ident = slave_idents;
|
|
while (ident->type) {
|
|
if (unlikely(ident->vendor_id == slave->sii_vendor_id
|
|
&& ident->product_code == slave->sii_product_code)) {
|
|
slave->type = ident->type;
|
|
break;
|
|
}
|
|
ident++;
|
|
}
|
|
|
|
if (!slave->type)
|
|
EC_WARN("Unknown slave device (vendor 0x%08X, code 0x%08X) at"
|
|
" position %i.\n", slave->sii_vendor_id,
|
|
slave->sii_product_code, i);
|
|
|
|
// Does the slave support EoE?
|
|
if (slave->sii_mailbox_protocols & EC_MBOX_EOE) {
|
|
if (!(eoe = kmalloc(sizeof(ec_eoe_t), GFP_KERNEL))) {
|
|
EC_ERR("Failed to allocate memory for EoE-Object.\n");
|
|
return -1;
|
|
}
|
|
|
|
ec_eoe_init(eoe, slave);
|
|
list_add_tail(&eoe->list, &master->eoe_slaves);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Statistik-Ausgaben während des zyklischen Betriebs.
|
|
|
|
Diese Funktion sorgt dafür, dass Statistiken während des zyklischen
|
|
Betriebs bei Bedarf, aber nicht zu oft ausgegeben werden.
|
|
|
|
Die Ausgabe erfolgt gesammelt höchstens einmal pro Sekunde.
|
|
*/
|
|
|
|
void ec_master_output_stats(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
cycles_t t_now = get_cycles();
|
|
|
|
if (unlikely((u32) (t_now - master->stats.t_last) / cpu_khz > 1000)) {
|
|
if (master->stats.timeouts) {
|
|
EC_WARN("%i commands TIMED OUT!\n", master->stats.timeouts);
|
|
master->stats.timeouts = 0;
|
|
}
|
|
if (master->stats.delayed) {
|
|
EC_WARN("%i frame(s) DELAYED!\n", master->stats.delayed);
|
|
master->stats.delayed = 0;
|
|
}
|
|
if (master->stats.corrupted) {
|
|
EC_WARN("%i frame(s) CORRUPTED!\n", master->stats.corrupted);
|
|
master->stats.corrupted = 0;
|
|
}
|
|
if (master->stats.unmatched) {
|
|
EC_WARN("%i command(s) UNMATCHED!\n", master->stats.unmatched);
|
|
master->stats.unmatched = 0;
|
|
}
|
|
if (master->stats.eoe_errors) {
|
|
EC_WARN("%i EOE ERROR(S)!\n", master->stats.eoe_errors);
|
|
master->stats.eoe_errors = 0;
|
|
}
|
|
master->stats.t_last = t_now;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Wandelt eine ASCII-kodierte Bus-Adresse in einen Slave-Zeiger.
|
|
|
|
Gültige Adress-Strings sind Folgende:
|
|
- \a "X" = der X. Slave im Bus,
|
|
- \a "X:Y" = der Y. Slave hinter dem X. Buskoppler,
|
|
- \a "#X" = der Slave mit dem Alias X,
|
|
- \a "#X:Y" = der Y. Slave hinter dem Buskoppler mit dem Alias X.
|
|
|
|
X und Y fangen immer bei 0 an und können auch hexadezimal oder oktal
|
|
angegeben werden (mit entsprechendem Prefix).
|
|
|
|
\return Zeiger auf Slave bei Erfolg, sonst NULL
|
|
*/
|
|
|
|
ec_slave_t *ecrt_master_get_slave(const ec_master_t *master, /**< Master */
|
|
const char *address /**< Address-String */
|
|
)
|
|
{
|
|
unsigned long first, second;
|
|
char *remainder, *remainder2;
|
|
unsigned int i, alias_requested, alias_slave_index, alias_found;
|
|
int coupler_idx, slave_idx;
|
|
ec_slave_t *slave;
|
|
|
|
if (!address || address[0] == 0) return NULL;
|
|
|
|
alias_requested = 0;
|
|
alias_slave_index = 0;
|
|
if (address[0] == '#') {
|
|
alias_requested = 1;
|
|
address++;
|
|
}
|
|
|
|
first = simple_strtoul(address, &remainder, 0);
|
|
if (remainder == address) {
|
|
EC_ERR("Slave address \"%s\" - First number empty!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
if (alias_requested) {
|
|
alias_found = 0;
|
|
for (i = 0; i < master->slave_count; i++) {
|
|
if (master->slaves[i].sii_alias == first) {
|
|
alias_slave_index = i;
|
|
alias_found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!alias_found) {
|
|
EC_ERR("Slave address \"%s\" - Alias not found!\n", address);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!remainder[0]) { // absolute position
|
|
if (alias_requested) {
|
|
return master->slaves + alias_slave_index;
|
|
}
|
|
else {
|
|
if (first < master->slave_count) {
|
|
return master->slaves + first;
|
|
}
|
|
EC_ERR("Slave address \"%s\" - Absolute position invalid!\n",
|
|
address);
|
|
}
|
|
}
|
|
else if (remainder[0] == ':') { // field position
|
|
remainder++;
|
|
second = simple_strtoul(remainder, &remainder2, 0);
|
|
|
|
if (remainder2 == remainder) {
|
|
EC_ERR("Slave address \"%s\" - Second number empty!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
if (remainder2[0]) {
|
|
EC_ERR("Slave address \"%s\" - Invalid trailer!\n", address);
|
|
return NULL;
|
|
}
|
|
|
|
if (alias_requested) {
|
|
for (i = alias_slave_index + 1; i < master->slave_count; i++) {
|
|
slave = master->slaves + i;
|
|
if (!slave->type ||
|
|
slave->type->special == EC_TYPE_BUS_COUPLER) break;
|
|
if (i - alias_slave_index == second) return slave;
|
|
}
|
|
EC_ERR("Slave address \"%s\" - Bus coupler %i has no %lu. slave"
|
|
" following!\n", address,
|
|
(master->slaves + alias_slave_index)->ring_position,
|
|
second);
|
|
return NULL;
|
|
}
|
|
else {
|
|
coupler_idx = -1;
|
|
slave_idx = 0;
|
|
for (i = 0; i < master->slave_count; i++, slave_idx++) {
|
|
slave = master->slaves + i;
|
|
if (!slave->type) continue;
|
|
if (slave->type->special == EC_TYPE_BUS_COUPLER) {
|
|
coupler_idx++;
|
|
slave_idx = 0;
|
|
}
|
|
if (coupler_idx == first && slave_idx == second) return slave;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
EC_ERR("Slave address \"%s\" - Invalid format!\n", address);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Initialisiert eine Sync-Manager-Konfigurationsseite.
|
|
|
|
Der mit \a data referenzierte Speicher muss mindestens EC_SYNC_SIZE Bytes
|
|
groß sein.
|
|
*/
|
|
|
|
void ec_sync_config(const ec_sync_t *sync, /**< Sync-Manager */
|
|
uint8_t *data /**> Zeiger auf Konfigurationsspeicher */
|
|
)
|
|
{
|
|
EC_WRITE_U16(data, sync->physical_start_address);
|
|
EC_WRITE_U16(data + 2, sync->size);
|
|
EC_WRITE_U8 (data + 4, sync->control_byte);
|
|
EC_WRITE_U8 (data + 5, 0x00); // status byte (read only)
|
|
EC_WRITE_U16(data + 6, 0x0001); // enable
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Initialisiert eine Sync-Manager-Konfigurationsseite mit EEPROM-Daten.
|
|
|
|
Der mit \a data referenzierte Speicher muss mindestens EC_SYNC_SIZE Bytes
|
|
groß sein.
|
|
*/
|
|
|
|
void ec_eeprom_sync_config(const ec_eeprom_sync_t *sync, /**< Sync-Manager */
|
|
uint8_t *data /**> Zeiger auf Konfig.-Speicher */
|
|
)
|
|
{
|
|
EC_WRITE_U16(data, sync->physical_start_address);
|
|
EC_WRITE_U16(data + 2, sync->length);
|
|
EC_WRITE_U8 (data + 4, sync->control_register);
|
|
EC_WRITE_U8 (data + 5, 0x00); // status byte (read only)
|
|
EC_WRITE_U16(data + 6, sync->enable ? 0x0001 : 0x0000); // enable
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Initialisiert eine FMMU-Konfigurationsseite.
|
|
|
|
Der mit \a data referenzierte Speicher muss mindestens EC_FMMU_SIZE Bytes
|
|
groß sein.
|
|
*/
|
|
|
|
void ec_fmmu_config(const ec_fmmu_t *fmmu, /**< Sync-Manager */
|
|
uint8_t *data /**> Zeiger auf Konfigurationsspeicher */
|
|
)
|
|
{
|
|
EC_WRITE_U32(data, fmmu->logical_start_address);
|
|
EC_WRITE_U16(data + 4, fmmu->sync->size);
|
|
EC_WRITE_U8 (data + 6, 0x00); // Logical start bit
|
|
EC_WRITE_U8 (data + 7, 0x07); // Logical end bit
|
|
EC_WRITE_U16(data + 8, fmmu->sync->physical_start_address);
|
|
EC_WRITE_U8 (data + 10, 0x00); // Physical start bit
|
|
EC_WRITE_U8 (data + 11, (fmmu->sync->control_byte & 0x04) ? 0x02 : 0x01);
|
|
EC_WRITE_U16(data + 12, 0x0001); // Enable
|
|
EC_WRITE_U16(data + 14, 0x0000); // res.
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Gibt Überwachungsinformationen aus.
|
|
*/
|
|
|
|
void ec_master_process_watch_command(ec_master_t *master
|
|
/**< EtherCAT-Master */
|
|
)
|
|
{
|
|
unsigned int first;
|
|
|
|
if (unlikely(master->watch_command.state == EC_CMD_INIT)) return;
|
|
|
|
first = 1;
|
|
|
|
if (master->watch_command.working_counter != master->slaves_responding ||
|
|
master->watch_command.data[0] != master->slave_states)
|
|
{
|
|
master->slaves_responding = master->watch_command.working_counter;
|
|
master->slave_states = master->watch_command.data[0];
|
|
|
|
EC_INFO("%i slave%s responding (", master->slaves_responding,
|
|
master->slaves_responding == 1 ? "" : "s");
|
|
|
|
if (master->slave_states & EC_SLAVE_STATE_INIT) {
|
|
printk("INIT");
|
|
first = 0;
|
|
}
|
|
if (master->slave_states & EC_SLAVE_STATE_PREOP) {
|
|
if (!first) printk(", ");
|
|
printk("PREOP");
|
|
first = 0;
|
|
}
|
|
if (master->slave_states & EC_SLAVE_STATE_SAVEOP) {
|
|
if (!first) printk(", ");
|
|
printk("SAVEOP");
|
|
first = 0;
|
|
}
|
|
if (master->slave_states & EC_SLAVE_STATE_OP) {
|
|
if (!first) printk(", ");
|
|
printk("OP");
|
|
}
|
|
printk(")\n");
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Echtzeitschnittstelle
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
Erstellt eine neue Domäne.
|
|
|
|
\return Zeiger auf die Domäne bei Erfolg, sonst NULL.
|
|
*/
|
|
|
|
ec_domain_t *ecrt_master_create_domain(ec_master_t *master /**< Master */)
|
|
{
|
|
ec_domain_t *domain;
|
|
|
|
if (!(domain = (ec_domain_t *) kmalloc(sizeof(ec_domain_t), GFP_KERNEL))) {
|
|
EC_ERR("Error allocating domain memory!\n");
|
|
return NULL;
|
|
}
|
|
|
|
ec_domain_init(domain, master);
|
|
list_add_tail(&domain->list, &master->domains);
|
|
|
|
return domain;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Konfiguriert alle Slaves und setzt den Operational-Zustand.
|
|
|
|
Führt die komplette Konfiguration und Aktivierunge aller registrierten
|
|
Slaves durch. Setzt Sync-Manager und FMMUs, führt die entsprechenden
|
|
Zustandsübergänge durch, bis der Slave betriebsbereit ist.
|
|
|
|
\return 0 bei Erfolg, sonst < 0
|
|
*/
|
|
|
|
int ecrt_master_activate(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
unsigned int i, j;
|
|
ec_slave_t *slave;
|
|
ec_command_t *command;
|
|
const ec_sync_t *sync;
|
|
const ec_slave_type_t *type;
|
|
const ec_fmmu_t *fmmu;
|
|
uint32_t domain_offset;
|
|
ec_domain_t *domain;
|
|
ec_eeprom_sync_t *eeprom_sync, mbox_sync;
|
|
|
|
command = &master->simple_command;
|
|
|
|
if (ec_command_brd(&master->watch_command, 0x130, 2)) {
|
|
EC_ERR("Failed to allocate watchdog command!\n");
|
|
return -1;
|
|
}
|
|
|
|
// Domains erstellen
|
|
domain_offset = 0;
|
|
list_for_each_entry(domain, &master->domains, list) {
|
|
if (ec_domain_alloc(domain, domain_offset)) {
|
|
EC_ERR("Failed to allocate domain %X!\n", (u32) domain);
|
|
return -1;
|
|
}
|
|
domain_offset += domain->data_size;
|
|
}
|
|
|
|
// Slaves aktivieren
|
|
for (i = 0; i < master->slave_count; i++)
|
|
{
|
|
slave = master->slaves + i;
|
|
|
|
// Change state to INIT
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_INIT)))
|
|
return -1;
|
|
|
|
// Check if slave was registered...
|
|
type = slave->type;
|
|
if (!type) EC_WARN("Slave %i has unknown type!\n", i);
|
|
|
|
// Check and reset CRC fault counters
|
|
ec_slave_check_crc(slave);
|
|
|
|
// Resetting FMMUs
|
|
if (slave->base_fmmu_count) {
|
|
if (ec_command_npwr(command, slave->station_address, 0x0600,
|
|
EC_FMMU_SIZE * slave->base_fmmu_count))
|
|
return -1;
|
|
memset(command->data, 0x00, EC_FMMU_SIZE * slave->base_fmmu_count);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Resetting FMMUs failed on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Resetting Sync Manager channels
|
|
if (slave->base_sync_count) {
|
|
if (ec_command_npwr(command, slave->station_address, 0x0800,
|
|
EC_SYNC_SIZE * slave->base_sync_count))
|
|
return -1;
|
|
memset(command->data, 0x00, EC_SYNC_SIZE * slave->base_sync_count);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Resetting sync managers failed on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Set Sync Managers
|
|
|
|
// Known slave type, take type's SM information
|
|
if (type) {
|
|
for (j = 0; type->sync_managers[j] && j < EC_MAX_SYNC; j++)
|
|
{
|
|
sync = type->sync_managers[j];
|
|
if (ec_command_npwr(command, slave->station_address,
|
|
0x0800 + j * EC_SYNC_SIZE, EC_SYNC_SIZE))
|
|
return -1;
|
|
ec_sync_config(sync, command->data);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Setting sync manager %i failed on slave %i!\n",
|
|
j, slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unknown slave type, but has mailbox
|
|
else if (slave->sii_mailbox_protocols) {
|
|
|
|
// Does the device supply SM configurations in its EEPROM?
|
|
if (!list_empty(&slave->eeprom_syncs)) {
|
|
list_for_each_entry(eeprom_sync, &slave->eeprom_syncs, list) {
|
|
EC_INFO("Sync manager %i...\n", eeprom_sync->index);
|
|
if (ec_command_npwr(command, slave->station_address,
|
|
0x800 + eeprom_sync->index *
|
|
EC_SYNC_SIZE,
|
|
EC_SYNC_SIZE)) return -1;
|
|
ec_eeprom_sync_config(eeprom_sync, command->data);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Setting sync manager %i failed on slave %i!\n",
|
|
eeprom_sync->index, slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No sync manager information; guess mailbox settings
|
|
else {
|
|
mbox_sync.physical_start_address =
|
|
slave->sii_rx_mailbox_offset;
|
|
mbox_sync.length = slave->sii_rx_mailbox_size;
|
|
mbox_sync.control_register = 0x26;
|
|
mbox_sync.enable = 1;
|
|
if (ec_command_npwr(command, slave->station_address,
|
|
0x800,EC_SYNC_SIZE)) return -1;
|
|
ec_eeprom_sync_config(&mbox_sync, command->data);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Setting sync manager 0 failed on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
|
|
mbox_sync.physical_start_address =
|
|
slave->sii_tx_mailbox_offset;
|
|
mbox_sync.length = slave->sii_tx_mailbox_size;
|
|
mbox_sync.control_register = 0x22;
|
|
mbox_sync.enable = 1;
|
|
if (ec_command_npwr(command, slave->station_address,
|
|
0x808, EC_SYNC_SIZE)) return -1;
|
|
ec_eeprom_sync_config(&mbox_sync, command->data);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Setting sync manager 1 failed on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
EC_INFO("Mailbox configured for unknown slave %i\n",
|
|
slave->ring_position);
|
|
}
|
|
|
|
// Change state to PREOP
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_PREOP)))
|
|
return -1;
|
|
|
|
// Stop activation here for slaves without type
|
|
if (!type) continue;
|
|
|
|
// Slaves that are not registered are only brought into PREOP
|
|
// state -> nice blinking and mailbox communication possible
|
|
if (!slave->registered && !slave->type->special) {
|
|
EC_WARN("Slave %i was not registered!\n", slave->ring_position);
|
|
continue;
|
|
}
|
|
|
|
// Set FMMUs
|
|
for (j = 0; j < slave->fmmu_count; j++)
|
|
{
|
|
fmmu = &slave->fmmus[j];
|
|
if (ec_command_npwr(command, slave->station_address,
|
|
0x0600 + j * EC_FMMU_SIZE, EC_FMMU_SIZE))
|
|
return -1;
|
|
ec_fmmu_config(fmmu, command->data);
|
|
if (unlikely(ec_master_simple_io(master, command))) {
|
|
EC_ERR("Setting FMMU %i failed on slave %i!\n",
|
|
j, slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Change state to SAVEOP
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_SAVEOP)))
|
|
return -1;
|
|
|
|
// Change state to OP
|
|
if (unlikely(ec_slave_state_change(slave, EC_SLAVE_STATE_OP)))
|
|
return -1;
|
|
}
|
|
|
|
master->slaves_responding = master->slave_count;
|
|
master->slave_states = EC_SLAVE_STATE_OP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Setzt alle Slaves zurück in den Init-Zustand.
|
|
*/
|
|
|
|
void ecrt_master_deactivate(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_slave_t *slave;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < master->slave_count; i++)
|
|
{
|
|
slave = master->slaves + i;
|
|
ec_slave_check_crc(slave);
|
|
ec_slave_state_change(slave, EC_SLAVE_STATE_INIT);
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Lädt die SDO-Dictionaries aller Slaves.
|
|
|
|
Slaves, die kein CoE unterstützen, werden ausgelassen.
|
|
|
|
\return 0 wenn alles ok, sonst < 0
|
|
*/
|
|
|
|
int ecrt_master_fetch_sdo_lists(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_slave_t *slave;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < master->slave_count; i++) {
|
|
slave = master->slaves + i;
|
|
if (slave->sii_mailbox_protocols & EC_MBOX_COE) {
|
|
if (unlikely(ec_slave_fetch_sdo_list(slave))) {
|
|
EC_ERR("Failed to fetch SDO list on slave %i!\n",
|
|
slave->ring_position);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet und empfängt Kommandos synchron.
|
|
*/
|
|
|
|
void ecrt_master_sync_io(ec_master_t *master)
|
|
{
|
|
ec_command_t *command, *n;
|
|
unsigned int commands_sent;
|
|
cycles_t t_start, t_end, t_timeout;
|
|
|
|
// Kommandos senden
|
|
ecrt_master_async_send(master);
|
|
|
|
t_start = get_cycles(); // Sendezeit nehmen
|
|
t_timeout = (cycles_t) master->timeout * (cpu_khz / 1000);
|
|
|
|
while (1) // Aktiv auf Empfang warten
|
|
{
|
|
ec_device_call_isr(master->device);
|
|
|
|
t_end = get_cycles(); // Aktuelle Zeit nehmen
|
|
if (t_end - t_start >= t_timeout) break; // Timeout
|
|
|
|
commands_sent = 0;
|
|
list_for_each_entry_safe(command, n, &master->command_queue, queue) {
|
|
if (command->state == EC_CMD_RECEIVED)
|
|
list_del_init(&command->queue);
|
|
else if (command->state == EC_CMD_SENT)
|
|
commands_sent++;
|
|
}
|
|
|
|
if (!commands_sent) break;
|
|
}
|
|
|
|
// Zeit abgelaufen. Alle verbleibenden Kommandos entfernen.
|
|
list_for_each_entry_safe(command, n, &master->command_queue, queue) {
|
|
switch (command->state) {
|
|
case EC_CMD_SENT:
|
|
case EC_CMD_QUEUED:
|
|
command->state = EC_CMD_TIMEOUT;
|
|
master->stats.timeouts++;
|
|
ec_master_output_stats(master);
|
|
break;
|
|
case EC_CMD_RECEIVED:
|
|
master->stats.delayed++;
|
|
ec_master_output_stats(master);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
list_del_init(&command->queue);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Sendet Kommandos asynchron.
|
|
*/
|
|
|
|
void ecrt_master_async_send(ec_master_t *master)
|
|
{
|
|
ec_command_t *command, *n;
|
|
|
|
if (unlikely(!master->device->link_state)) {
|
|
// Link DOWN, keines der Kommandos kann gesendet werden.
|
|
list_for_each_entry_safe(command, n, &master->command_queue, queue) {
|
|
command->state = EC_CMD_ERROR;
|
|
list_del_init(&command->queue);
|
|
}
|
|
|
|
// Device-Zustand abfragen
|
|
ec_device_call_isr(master->device);
|
|
return;
|
|
}
|
|
|
|
// Rahmen senden
|
|
ec_master_send_commands(master);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Empfängt Kommandos asynchron.
|
|
*/
|
|
|
|
void ecrt_master_async_receive(ec_master_t *master)
|
|
{
|
|
ec_command_t *command, *next;
|
|
|
|
ec_device_call_isr(master->device);
|
|
|
|
// Alle empfangenen Kommandos aus der Liste entfernen
|
|
list_for_each_entry_safe(command, next, &master->command_queue, queue)
|
|
if (command->state == EC_CMD_RECEIVED) list_del_init(&command->queue);
|
|
|
|
// Alle verbleibenden Kommandos entfernen.
|
|
list_for_each_entry_safe(command, next, &master->command_queue, queue) {
|
|
switch (command->state) {
|
|
case EC_CMD_SENT:
|
|
case EC_CMD_QUEUED:
|
|
command->state = EC_CMD_TIMEOUT;
|
|
master->stats.timeouts++;
|
|
ec_master_output_stats(master);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
list_del_init(&command->queue);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Bereitet Synchronen Datenverkehr vor.
|
|
|
|
Fürgt einmal die Kommandos aller Domains zur Warteschlange hinzu, sendet
|
|
diese ab und wartet so lange, bis diese anschließend problemlos empfangen
|
|
werden können.
|
|
*/
|
|
|
|
void ecrt_master_prepare_async_io(ec_master_t *master)
|
|
{
|
|
ec_domain_t *domain;
|
|
cycles_t t_start, t_end, t_timeout;
|
|
|
|
// Kommandos aller Domains in die Warteschlange setzen
|
|
list_for_each_entry(domain, &master->domains, list)
|
|
ecrt_domain_queue(domain);
|
|
|
|
ecrt_master_async_send(master);
|
|
|
|
t_start = get_cycles(); // Sendezeit nehmen
|
|
t_timeout = (cycles_t) master->timeout * (cpu_khz / 1000);
|
|
|
|
// Aktiv warten!
|
|
while (1) {
|
|
t_end = get_cycles();
|
|
if (t_end - t_start >= t_timeout) break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Führt Routinen im zyklischen Betrieb aus.
|
|
*/
|
|
|
|
void ecrt_master_run(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
// Statistiken ausgeben
|
|
ec_master_output_stats(master);
|
|
|
|
// Watchdog-Kommando
|
|
ec_master_process_watch_command(master);
|
|
ec_master_queue_command(master, &master->watch_command);
|
|
|
|
// Ethernet-over-EtherCAT
|
|
ec_master_run_eoe(master);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Setzt die Debug-Ebene des Masters.
|
|
|
|
Folgende Debug-Level sind definiert:
|
|
|
|
- 1: Nur Positionsmarken in bestimmten Funktionen
|
|
- 2: Komplette Frame-Inhalte
|
|
*/
|
|
|
|
void ecrt_master_debug(ec_master_t *master, /**< EtherCAT-Master */
|
|
int level /**< Debug-Level */
|
|
)
|
|
{
|
|
if (level != master->debug_level) {
|
|
master->debug_level = level;
|
|
EC_INFO("Master debug level set to %i.\n", level);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
Gibt alle Informationen zum Master aus.
|
|
|
|
Verbosity:
|
|
0 - Nur Slavetypen und Adressen
|
|
1 - mit EEPROM-Informationen
|
|
>1 - mit SDO-Dictionaries
|
|
*/
|
|
|
|
void ecrt_master_print(const ec_master_t *master, /**< EtherCAT-Master */
|
|
unsigned int verbosity /**< Geschwätzigkeit */
|
|
)
|
|
{
|
|
unsigned int i;
|
|
ec_eoe_t *eoe;
|
|
|
|
EC_INFO("*** Begin master information ***\n");
|
|
if (master->slave_count) {
|
|
EC_INFO("Slave list:\n");
|
|
for (i = 0; i < master->slave_count; i++)
|
|
ec_slave_print(&master->slaves[i], verbosity);
|
|
}
|
|
if (!list_empty(&master->eoe_slaves)) {
|
|
EC_INFO("Ethernet-over-EtherCAT (EoE) objects:\n");
|
|
list_for_each_entry(eoe, &master->eoe_slaves, list) {
|
|
ec_eoe_print(eoe);
|
|
}
|
|
}
|
|
EC_INFO("*** End master information ***\n");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void ec_master_run_eoe(ec_master_t *master /**< EtherCAT-Master */)
|
|
{
|
|
ec_eoe_t *eoe;
|
|
|
|
list_for_each_entry(eoe, &master->eoe_slaves, list) {
|
|
ec_eoe_run(eoe);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
EXPORT_SYMBOL(ecrt_master_create_domain);
|
|
EXPORT_SYMBOL(ecrt_master_activate);
|
|
EXPORT_SYMBOL(ecrt_master_deactivate);
|
|
EXPORT_SYMBOL(ecrt_master_fetch_sdo_lists);
|
|
EXPORT_SYMBOL(ecrt_master_prepare_async_io);
|
|
EXPORT_SYMBOL(ecrt_master_sync_io);
|
|
EXPORT_SYMBOL(ecrt_master_async_send);
|
|
EXPORT_SYMBOL(ecrt_master_async_receive);
|
|
EXPORT_SYMBOL(ecrt_master_run);
|
|
EXPORT_SYMBOL(ecrt_master_debug);
|
|
EXPORT_SYMBOL(ecrt_master_print);
|
|
EXPORT_SYMBOL(ecrt_master_get_slave);
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* Emacs-Konfiguration
|
|
;;; Local Variables: ***
|
|
;;; c-basic-offset:4 ***
|
|
;;; End: ***
|
|
*/
|