探索Modbus通信库:libmodbus版本检测与功能探索

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

探索Modbus通信库:libmodbus版本检测与功能探索缩略图
🌐 This page is not yet available in English. Showing the Chinese version. Back to Chinese page.
本文目录
  1. 1. 一、libmodbus库概述
  2. 2. 二、版本检测方法
  3. 3. 三、编译安装
  4. 4. 四、核心API详解
  5. 5. 五、Modbus RTU串口通讯
  6. 6. 六、Modbus TCPServer(slave)实现
  7. 7. 七、完整工具类封装
  8. 8. 八、编译与链接
  9. 9. 九、常见问题与最佳实践
  10. 10. 十、libmodbus与其他C语言Modbus库对比

libmodbus是C语言生态中最流行、最成熟的Modbus协议库,由Stéphane Raimbault开发维护,广泛应用于工业自动化、嵌入式系统、物联网网关等领域。本文详细介绍libmodbus库的版本检测方法、核心功能、API使用、编译安装以及完整的示例代码,帮助开发者快速掌握libmodbus的使用方法。

一、libmodbus库概述

1.1 核心特性

  • 多协议支持:Modbus RTU、Modbus TCP、Modbus ASCII三种协议
  • 主从兼备:既可作为客户端(master),也可作为服务端(slave)
  • 跨平台:支持Linux、Windows、Mac OS、FreeBSD、embeddedLinux等
  • 功能完整:支持01-16所有标准功能码,以及部分自定义功能码
  • 高性能:C语言实现,资源占用小,适合嵌入式环境
  • 线程安全:每个modbus_t上下文独立,多线程使用需各自创建实例
  • 开源免费:LGPL 2.1协议,可免费用于商业项目
  • 文档完善:官方文档详细,示例丰富

1.2 版本历史

版本Published主要更新
3.1.102023年最新稳定版,修复多个bug
3.1.02020年增加Modbus TCP服务器支持,改进RTU
3.0.02018年重大重构,API变化
2.9.32017年2.x系列最后版本
2.0.02012年增加TCP支持,改进RTU

二、版本检测方法

2.1 编译时版本检测

#include <stdio.h>
#include <modbus.h>

int main() {
    // libmodbus提供了版本宏
    printf("libmodbus版本:%s\n", LIBMODBUS_VERSION_STRING);
    printf("主版本号:%d\n", LIBMODBUS_VERSION_MAJOR);
    printf("次版本号:%d\n", LIBMODBUS_VERSION_MINOR);
    printf("补丁版本号:%d\n", LIBMODBUS_VERSION_MICRO);
    
    // 版本号数值形式,便于比较(部分版本可能未定义此宏)
#ifdef LIBMODBUS_VERSION
    printf("版本数值:%d\n", LIBMODBUS_VERSION);
#endif
    
    // 示例输出:
    // libmodbus版本:3.1.10
    // 主版本号:3
    // 次版本号:1
    // 补丁版本号:10
    // 版本数值:3.1.10
    
    return 0;
}

2.2 运行时版本检测

#include <stdio.h>
#include <modbus.h>
#include <dlfcn.h>

/* 运行时检测libmodbus版本(用于动态链接库) */
void check_libmodbus_version() {
    // 方法1:使用版本宏(编译时确定)
    printf("编译时版本:%s\n", LIBMODBUS_VERSION_STRING);
    
    // 方法2:通过pkg-config查询(命令行)
    // pkg-config --modversion libmodbus
    
    // 方法3:检查特定函数是否存在
    // modbus_new_tcp_pi是3.0+版本的函数
    void *handle = dlopen("libmodbus.so", RTLD_LAZY);
    if (handle) {
        void *func = dlsym(handle, "modbus_new_tcp_pi");
        if (func) {
            printf("检测到modbus_new_tcp_pi函数,版本>=3.0\n");
        } else {
            printf("未检测到modbus_new_tcp_pi函数,版本<3.0\n");
        }
        dlclose(handle);
    }
}

int main() {
    check_libmodbus_version();
    return 0;
}

2.3 命令行版本检测

# 方法1:pkg-config
pkg-config --modversion libmodbus
# 输出:3.1.10

# 方法2:查看库文件
ls -la /usr/lib/x86_64-linux-gnu/libmodbus*
# 输出:libmodbus.so.5 -> libmodbus.so.5.1.0

# 方法3:dpkg(Debian/Ubuntu)
dpkg -l | grep libmodbus
# 输出:ii  libmodbus-dev  3.1.10-1  amd64

# 方法4:rpm(CentOS/RHEL)
rpm -qa | grep modbus
# 输出:libmodbus-3.1.10-1.el8.x86_64

# 方法5:编译测试程序
cat > test_version.c << 'EOF'
#include <stdio.h>
#include <modbus.h>
int main() { printf("%s\n", LIBMODBUS_VERSION_STRING); return 0; }
EOF
gcc test_version.c -o test_version `pkg-config --cflags --libs libmodbus`
./test_version
# 输出:3.1.10

三、编译安装

3.1 Linux源码编译

# 安装依赖
sudo apt-get install build-essential autoconf automake libtool

# 下载源码
wget https://github.com/stephane/libmodbus/archive/refs/tags/v3.1.10.tar.gz
tar xzf v3.1.10.tar.gz
cd libmodbus-3.1.10

# 编译安装
./autogen.sh
./configure --prefix=/usr/local
make -j4
sudo make install
sudo ldconfig

# 验证安装
pkg-config --modversion libmodbus
# 输出:3.1.10

3.2 包管理器安装

# Debian/Ubuntu
sudo apt-get install libmodbus-dev

# CentOS/RHEL
sudo yum install libmodbus-devel

# Fedora
sudo dnf install libmodbus-devel

# Arch Linux
sudo pacman -S libmodbus

# Mac OS (Homebrew)
brew install libmodbus

3.3 Windows编译

# 使用MSYS2/MinGW编译
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-libmodbus

# 或者使用CMake
mkdir build
cd build
cmake .. -G "MinGW Makefiles"
mingw32-make
mingw32-make install

四、核心API详解

4.1 上下文创建与连接

#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
#include <errno.h>

int main() {
    modbus_t *ctx;
    int rc;

    /* === 创建TCP上下文 === */
    ctx = modbus_new_tcp("192.168.1.100", 502);
    if (ctx == NULL) {
        fprintf(stderr, "创建TCP上下文失败:%s\n", modbus_strerror(errno));
        return -1;
    }

    /* === 创建RTU上下文 === */
    // ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
    // modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);

    /* === 设置从站地址 === */
    modbus_set_slave(ctx, 1);

    /* === 设置调试模式(打印通讯数据) === */
    modbus_set_debug(ctx, TRUE);

    /* === 设置响应超时 === */
    struct timeval response_timeout;
    response_timeout.tv_sec = 3;
    response_timeout.tv_usec = 0;
    modbus_set_response_timeout(ctx, &response_timeout);

    /* === 建立连接 === */
    rc = modbus_connect(ctx);
    if (rc == -1) {
        fprintf(stderr, "连接失败:%s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    printf("连接成功!\n");

    /* === 业务逻辑 === */
    // ...

    /* === 关闭连接 === */
    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

4.2 读操作

/* function code01:读线圈 */
uint8_t tab_coils[MODBUS_MAX_READ_BITS];  // 最多2000位
rc = modbus_read_bits(ctx, 0, 8, tab_coils);
if (rc == -1) {
    fprintf(stderr, "读线圈失败:%s\n", modbus_strerror(errno));
} else {
    printf("读取到%d个线圈状态:\n", rc);
    for (int i = 0; i < rc; i++) {
        printf("  线圈%d:%s\n", i, tab_coils[i] ? "吸合" : "断开");
    }
}

/* function code02:read discrete inputs */
uint8_t tab_inputs[MODBUS_MAX_READ_BITS];
rc = modbus_read_input_bits(ctx, 0, 4, tab_inputs);

/* function code03:read holding registers */
uint16_t tab_reg[MODBUS_MAX_READ_REGISTERS];  // 最多125个
rc = modbus_read_registers(ctx, 0, 10, tab_reg);
if (rc == -1) {
    fprintf(stderr, "读寄存器失败:%s\n", modbus_strerror(errno));
} else {
    printf("读取到%dRegisters:\n", rc);
    for (int i = 0; i < rc; i++) {
        printf("  register%d:%d\n", i, tab_reg[i]);
    }
}

/* function code04:read input registers */
uint16_t tab_input_reg[MODBUS_MAX_READ_REGISTERS];
rc = modbus_read_input_registers(ctx, 0, 2, tab_input_reg);
if (rc != -1) {
    float temperature = tab_input_reg[0] / 10.0;
    float humidity = tab_input_reg[1] / 10.0;
    printf("temperature:%.1f℃,湿度:%.1f%%\n", temperature, humidity);
}

4.3 写操作

/* function code05:write single coil */
rc = modbus_write_bit(ctx, 0, TRUE);   // 吸合
if (rc == -1) {
    fprintf(stderr, "写线圈失败:%s\n", modbus_strerror(errno));
}
modbus_write_bit(ctx, 0, FALSE);  // 断开

/* function code06:write single register */
rc = modbus_write_register(ctx, 0, 1234);
if (rc == -1) {
    fprintf(stderr, "写寄存器失败:%s\n", modbus_strerror(errno));
}

/* function code15:write multiple coils */
uint8_t tab_write_coils[] = {TRUE, FALSE, TRUE, FALSE};
rc = modbus_write_bits(ctx, 0, 4, tab_write_coils);

/* function code16:write multiple registers */
uint16_t tab_write_reg[] = {100, 200, 300};
rc = modbus_write_registers(ctx, 0, 3, tab_write_reg);

4.4 浮点数处理

/* read32位浮点数(ABCD大端) */
uint16_t regs[2];
modbus_read_registers(ctx, 10, 2, regs);

// 方法1:使用modbus_get_float
float value = modbus_get_float(regs);
printf("浮点数值:%.2f\n", value);

// 方法2:手动转换
uint32_t bits = ((uint32_t)regs[0] << 16) | regs[1];
float value2;
memcpy(&value2, &bits, sizeof(float));
printf("浮点数值:%.2f\n", value2);

/* 写入32位浮点数 */
float write_value = 25.5;
uint16_t write_regs[2];
modbus_set_float(write_value, write_regs);
modbus_write_registers(ctx, 10, 2, write_regs);

/* 不同字节序的浮点数处理(libmodbus 3.1+) */
// modbus_get_float_abcd  → ABCD(大端,高字高字节)
// modbus_get_float_cdab  → CDAB(Word exchange,低字高字节)
// modbus_get_float_dcba  → DCBA(小端,低字低字节)
// modbus_get_float_badc  → BADC(字节交换,高字低字节)
// modbus_get_float       → 默认等同于 abcd(大端)
//
// 字节序说明(以32位值0x41420000为例,A=0x41,B=0x42,C=0x00,D=0x00):
//   ABCD: reg[0]=0x4142, reg[1]=0x0000  (标准大端,高字高字节)
//   CDAB: reg[0]=0x0000, reg[1]=0x4142  (Word exchange,低字在前)
//   DCBA: reg[0]=0x0000, reg[1]=0x4241  (小端,低字低字节)
//   BADC: reg[0]=0x4241, reg[1]=0x0000  (字节交换,高字低字节)

五、Modbus RTU串口通讯

#include <modbus.h>
#include <modbus-rtu.h>

int main() {
    modbus_t *ctx;
    uint16_t regs[10];

    // 创建RTU上下文
    ctx = modbus_new_rtu("/dev/ttyUSB0",  // Serial Port Device
                         9600,            // Baud rate
                         'N',             // check digit(N/E/O)
                         8,               // data bit
                         1);              // stop bit
    if (ctx == NULL) {
        fprintf(stderr, "创建RTU上下文失败\n");
        return -1;
    }

    // 设置RS485模式
    modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    // MODBUS_RTU_RS232 = RS232模式
    // MODBUS_RTU_RS485 = RS485模式

    // 设置RTS模式(部分RS485转换器需要)
    modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP);
    // MODBUS_RTU_RTS_NONE = 不控制RTS
    // MODBUS_RTU_RTS_UP = 发送时RTS高电平
    // MODBUS_RTU_RTS_DOWN = 发送时RTS低电平

    // 设置从站地址
    modbus_set_slave(ctx, 1);

    // connect
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "RTU连接失败:%s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // Read and hold register
    int rc = modbus_read_registers(ctx, 0, 10, regs);
    if (rc != -1) {
        for (int i = 0; i < rc; i++) {
            printf("register%d:%d\n", i, regs[i]);
        }
    }

    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

六、Modbus TCPServer(slave)实现

#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
#include <modbus-tcp.h>
#include <unistd.h>

#define NB_COILS     1024
#define NB_INPUTS    1024
#define NB_REGISTERS 1024

int main() {
    modbus_t *ctx;
    modbus_mapping_t *mb_mapping;
    int server_socket;
    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    int rc;

    // 创建TCP上下文(监听所有地址)
    ctx = modbus_new_tcp("0.0.0.0", 502);
    if (ctx == NULL) {
        fprintf(stderr, "创建上下文失败\n");
        return -1;
    }

    // 创建数据映射
    mb_mapping = modbus_mapping_new(
        NB_COILS,      // 线圈数量
        NB_INPUTS,     // 离散输入数量
        NB_REGISTERS,  // 保持寄存器数量
        NB_REGISTERS   // 输入寄存器数量
    );
    if (mb_mapping == NULL) {
        fprintf(stderr, "创建数据映射失败:%s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 设置初始值
    mb_mapping->tab_registers[0] = 100;  // temperature
    mb_mapping->tab_registers[1] = 50;   // 湿度
    mb_mapping->tab_bits[0] = TRUE;      // Relay1

    // 启动TCP监听
    server_socket = modbus_tcp_listen(ctx, 5);  // 最大5个连接
    if (server_socket == -1) {
        fprintf(stderr, "监听失败:%s\n", modbus_strerror(errno));
        modbus_mapping_free(mb_mapping);
        modbus_free(ctx);
        return -1;
    }
    printf("Modbus TCP服务器已启动,监听端口:502\n");

    // 主循环
    for (;;) {
        // 接受客户端连接
        int client_socket = modbus_tcp_accept(ctx, &server_socket);
        if (client_socket == -1) {
            continue;
        }
        printf("客户端连接\n");

        // 处理客户端请求
        for (;;) {
            rc = modbus_receive(ctx, query);
            if (rc > 0) {
                // 回复请求
                modbus_reply(ctx, query, rc, mb_mapping);
            } else {
                // 连接断开或错误
                break;
            }
        }

        printf("客户端断开\n");
        close(client_socket);
    }

    close(server_socket);
    modbus_mapping_free(mb_mapping);
    modbus_free(ctx);
    return 0;
}

七、完整工具类封装

/* modbus_util.h */
#ifndef MODBUS_UTIL_H
#define MODBUS_UTIL_H

#include <modbus.h>

typedef struct {
    modbus_t *ctx;
    const char *host;
    int port;
    int slave_id;
} ModbusUtil;

/* 创建并连接 */
ModbusUtil* modbus_util_connect(const char *host, int port, int slave_id);

/* 断开连接 */
void modbus_util_disconnect(ModbusUtil *mu);

/* read holding registers(带重试) */
int modbus_util_read_regs(ModbusUtil *mu, int addr, int count, uint16_t *regs);

/* 读32位浮点数 */
float modbus_util_read_float(ModbusUtil *mu, int addr);

/* 写32位浮点数 */
int modbus_util_write_float(ModbusUtil *mu, int addr, float value);

/* write single register */
int modbus_util_write_reg(ModbusUtil *mu, int addr, uint16_t value);

/* write single coil */
int modbus_util_write_coil(ModbusUtil *mu, int addr, int value);

/* 读线圈 */
int modbus_util_read_coils(ModbusUtil *mu, int addr, int count, uint8_t *coils);

#endif

/* modbus_util.c */
#include "modbus_util.h"
#include <stdio.h>
#include <unistd.h>

ModbusUtil* modbus_util_connect(const char *host, int port, int slave_id) {
    ModbusUtil *mu = malloc(sizeof(ModbusUtil));
    mu->host = host;
    mu->port = port;
    mu->slave_id = slave_id;

    for (int attempt = 0; attempt < 3; attempt++) {
        mu->ctx = modbus_new_tcp(host, port);
        if (mu->ctx == NULL) continue;

        modbus_set_slave(mu->ctx, slave_id);
        struct timeval tv = {3, 0};
        modbus_set_response_timeout(mu->ctx, &tv);

        if (modbus_connect(mu->ctx) != -1) {
            printf("连接成功:%s:%d\n", host, port);
            return mu;
        }
        modbus_free(mu->ctx);
        sleep(1);
    }
    free(mu);
    return NULL;
}

void modbus_util_disconnect(ModbusUtil *mu) {
    if (mu) {
        modbus_close(mu->ctx);
        modbus_free(mu->ctx);
        free(mu);
    }
}

int modbus_util_read_regs(ModbusUtil *mu, int addr, int count, uint16_t *regs) {
    for (int attempt = 0; attempt < 3; attempt++) {
        int rc = modbus_read_registers(mu->ctx, addr, count, regs);
        if (rc != -1) return rc;
        fprintf(stderr, "Read failed(No.%d次):%s\n", attempt+1, modbus_strerror(errno));
        modbus_close(mu->ctx);
        modbus_connect(mu->ctx);
    }
    return -1;
}

float modbus_util_read_float(ModbusUtil *mu, int addr) {
    uint16_t regs[2];
    if (modbus_util_read_regs(mu, addr, 2, regs) != 2) return 0;
    return modbus_get_float(regs);
}

int modbus_util_write_float(ModbusUtil *mu, int addr, float value) {
    uint16_t regs[2];
    modbus_set_float(value, regs);
    return modbus_write_registers(mu->ctx, addr, 2, regs);
}

int modbus_util_write_reg(ModbusUtil *mu, int addr, uint16_t value) {
    return modbus_write_register(mu->ctx, addr, value);
}

int modbus_util_write_coil(ModbusUtil *mu, int addr, int value) {
    return modbus_write_bit(mu->ctx, addr, value);
}

int modbus_util_read_coils(ModbusUtil *mu, int addr, int count, uint8_t *coils) {
    return modbus_read_bits(mu->ctx, addr, count, coils);
}

/* 使用示例 */
int main() {
    ModbusUtil *mb = modbus_util_connect("192.168.1.100", 502, 1);
    if (!mb) {
        fprintf(stderr, "连接失败\n");
        return -1;
    }

    // 读取温度
    float temp = modbus_util_read_float(mb, 10);
    printf("temperature:%.2f℃\n", temp);

    // read10个寄存器
    uint16_t regs[10];
    modbus_util_read_regs(mb, 0, 10, regs);

    // 控制继电器
    modbus_util_write_coil(mb, 0, TRUE);

    modbus_util_disconnect(mb);
    return 0;
}

八、编译与链接

# 简单编译
gcc main.c -o main `pkg-config --cflags --libs libmodbus`

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(modbus_demo)

find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBMODBUS REQUIRED libmodbus)

add_executable(main main.c)
target_include_directories(main PRIVATE ${LIBMODBUS_INCLUDE_DIRS})
target_link_libraries(main ${LIBMODBUS_LIBRARIES})

# Makefile
CC = gcc
CFLAGS = `pkg-config --cflags libmodbus` -Wall
LDFLAGS = `pkg-config --libs libmodbus`

main: main.c
	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)

clean:
	rm -f main

九、常见问题与最佳实践

9.1 common problems

  • 连接超时:检查IPaddress、端口、防火墙,确认设备在线
  • 返回异常码:检查从站地址、function code、地址范围是否正确
  • RTU串口打不开:检查串口设备名,确认权限(加入dialout组)
  • CRCError:RTU模式下libmodbus自动处理CRC,不要手动添加
  • 浮点数解析错误:确认设备字节序,使用对应的函数(modbus_get_float_abcd/cdab/dcba/badc)
  • 多线程问题:每个线程使用独立的modbus_t上下文,不要共享
  • 内存泄漏:使用modbus_mapping_free释放映射,modbus_free释放上下文

9.2 最佳实践

  • 使用pkg-config:通过pkg-config获取编译参数,避免路径问题
  • 批量读取:尽量一次读取多个连续寄存器,减少通讯次数
  • 合理超时:局域网1-3秒,RTUserial port3-5秒
  • 添加重试:网络不稳定时自动重试2-3次
  • 错误处理:每次调用检查返回值,使用modbus_strerror获取错误描述
  • 调试模式:开发阶段开启modbus_set_debug,查看原始通讯数据
  • 资源释放:程序退出前调用modbus_close和modbus_free
  • 版本兼容:使用LIBMODBUS_VERSION_CHECK宏检测版本,确保API兼容

十、libmodbus与其他C语言Modbus库对比

库名协议支持服务端支持活跃度适用场景
libmodbusRTU/TCP/ASCII支持大多数项目首选
freemodbusRTU/ASCII仅从站嵌入式从站设备
modbus-arduinoRTU/TCP支持Arduino平台
mbusRTU/TCP仅主站简单主站应用

libmodbus是C语言Modbus开发的首选库,其完善的功能和活跃的社区使其在工业自动化、嵌入式系统、物联网网关等领域得到广泛应用。本文提供的版本检测方法、API详解和工具类封装可直接用于项目开发,建议先使用Modbus Poll确认设备通讯正常,再将C代码集成到实际项目中。

技术术语(共 12 个)—— Click to Expand
Modbus RTU基于串行链路的ModbusProtocol,使用二进制编码和CRC check
Modbus TCP基于以太网的Modbus协议变体,使用TCP/IP传输
Modbus ASCII使用ASCII字符传输的ModbusProtocol,以冒号开头、CR/LF结尾
RS485工业常用的差分串行通信标准,支持多点通信
RS232点对点串行通信标准,常用于短距离设备通信
function codeModbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
registerModbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
Baud rate串行通信每秒传输符号数,Modbus RTU常用9600/19200
网关协议转换设备,如 Modbus RTU ↔ Modbus TCP
serial port计算机与外部设备进行串行通信的物理接口
线圈Modbus位可读写数据,地址从00001开始
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— Click to Expand
来源 Modbus Chinese Network(modbus.cn) —— China leadingModbuscommunication protocol technical community Category Modbus programming development 字数 12239 字 · 阅读约 31 分钟 更新 2024-04-21 永久链接 https://www.modbus.cn/27725.html
Recommended Tool: Modbus Debug Assistant WeChat Mini Program
Modbus Chinese Network官方推出的Modbus debugging tool,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 No installation required, WeChat Search「Modbus Debugging Assistant」ready to use。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
Related Tags
Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

Leave a Reply

Your email address will not be published. Required fields are marked *.