linux 内核裁剪之 rootfs

说明

编译成功以后,貌似需要做很多其它的工作

步骤

源码

1
https://busybox.net/downloads/busybox-1.36.1.tar.bz2
1
2
3
4
mkdir output

make defconfig
make menuconfig

配置静态编译

1
2
3
# 不然会提示找不到 working dir
Settings --->
[*] Build static binary (no shared libs)

编译

1
make -j$(nproc) CONFIG_PREFIX=$(pwd)/../rootfs/ install

自定义脚本

patch_fs.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/bin/bash

if [ "$(id -u)" -ne 0 ]; then
echo "需要 root 权限。" >&2
exit 1
fi

TTY_NAME="ttyAMA0"
HOST_NAME="xxx"

# 123456
ROOT_PASSWORD_HASH='$1$B3A6v4ws$ydvwSpAz0PYhYss1WOxOy1'

BUSYBOX_SRC_DIR="$(pwd)/busybox-1.36.1"
WORK_DIR="$(pwd)/"
ROOTFS_DIR="$(pwd)/rootfs"
IMAGE_NAME="rootfs.ext4"
IMAGE_SIZE_MB=32
TEMP_MOUNT_DIR="${WORK_DIR}/fs_mnt_tmp"

cleanup() {
echo "清理临时挂载点(如果存在)..."
if mountpoint -q "${TEMP_MOUNT_DIR}"; then
umount "${TEMP_MOUNT_DIR}"
fi
rm -rf "${TEMP_MOUNT_DIR}"
echo "清理完成。"
}

echo "开始执行 BusyBox rootfs 准备脚本 (以 root 权限运行)..."
set -e

if [ ! -d "${BUSYBOX_SRC_DIR}" ]; then
echo "错误:BusyBox 源码目录 ${BUSYBOX_SRC_DIR} 未找到!"
exit 1
fi

mkdir -p "${ROOTFS_DIR}"
echo "Rootfs 目录: ${ROOTFS_DIR}"
echo "输出镜像文件: ${WORK_DIR}/${IMAGE_NAME}"

echo "--- 检查 BusyBox 安装 ---"
BUSYBOX_EXEC_PATH="${ROOTFS_DIR}/bin/busybox"
if [ ! -x "${BUSYBOX_EXEC_PATH}" ]; then
echo "错误:${BUSYBOX_EXEC_PATH} 不存在或不可执行!"
echo "请提前准备好 BusyBox 安装到 ${ROOTFS_DIR} 目录。"
exit 1
fi
echo "BusyBox 主程序 (${BUSYBOX_EXEC_PATH}) 存在且可执行。"

echo "使用 'file' 命令检查 BusyBox 可执行文件类型:"
file "${BUSYBOX_EXEC_PATH}"

INIT_PATH_FOUND=""
if [ -L "${ROOTFS_DIR}/sbin/init" ] && [ "$(readlink -f "${ROOTFS_DIR}/sbin/init")" = "${BUSYBOX_EXEC_PATH}" ]; then
INIT_PATH_FOUND="${ROOTFS_DIR}/sbin/init"
elif [ -L "${ROOTFS_DIR}/bin/init" ] && [ "$(readlink -f "${ROOTFS_DIR}/bin/init")" = "${BUSYBOX_EXEC_PATH}" ]; then
INIT_PATH_FOUND="${ROOTFS_DIR}/bin/init"
fi

if [ -z "${INIT_PATH_FOUND}" ]; then
echo "错误:未找到指向 busybox 的 init 符号链接!"
exit 1
fi
echo "init 程序 (${INIT_PATH_FOUND}) 存在且正确链接到 busybox。"

if ! ([ -L "${ROOTFS_DIR}/bin/sh" ] && [ "$(readlink -f "${ROOTFS_DIR}/bin/sh")" = "${BUSYBOX_EXEC_PATH}" ]); then
echo "错误:${ROOTFS_DIR}/bin/sh 未正确链接到 busybox!"
exit 1
fi
echo "sh 程序 (${ROOTFS_DIR}/bin/sh) 存在且正确链接到 busybox。"
echo "BusyBox 安装验证通过。"

cd "${ROOTFS_DIR}"

# 创建必需目录
mkdir -p dev dev/pts dev/shm etc lib usr/bin usr/sbin usr/lib var/log var/run var/tmp home root mnt proc sys tmp

# 建立 usr -> bin, sbin, lib 软连接(如果不存在)
if [ -d usr/bin ] && [ ! -e bin ]; then ln -sf usr/bin bin; fi
if [ -d usr/sbin ] && [ ! -e sbin ]; then ln -sf usr/sbin sbin; fi
if [ -d usr/lib ] && [ ! -e lib ]; then ln -sf usr/lib lib; fi

echo "--- 创建配置文件 ---"

cat > etc/profile << EOF
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export LD_LIBRARY_PATH=/lib:/usr/lib
/bin/hostname ${HOST_NAME}
USER=root
LOGNAME=\$USER
HOSTNAME=\$(/bin/hostname)
PS1='[\u@\h \W]\\# '
EOF

cat > etc/inittab << EOF
::sysinit:/etc/init.d/rcS

#${TTY_NAME}::respawn:/sbin/getty -L ${TTY_NAME} 115200 vt100

# 免密
${TTY_NAME}::respawn:/bin/sh </dev/${TTY_NAME} >/dev/${TTY_NAME} 2>&1

::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
EOF

cat > etc/fstab << EOF
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devtmpfs /dev devtmpfs mode=0755,nosuid 0 0
tmpfs /tmp tmpfs defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
none /var ramfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=0620 0 0
EOF

echo "${HOST_NAME}" > etc/hostname

cat > etc/passwd << EOF
root:x:0:0:root:/root:/bin/sh
EOF
chmod 644 etc/passwd

cat > etc/group << EOF
root:x:0:
EOF
chmod 644 etc/group

cat > etc/shadow << EOF
root:${ROOT_PASSWORD_HASH}:10933:0:99999:7:::
EOF
chmod 600 etc/shadow

mkdir -p etc/init.d
cat > etc/init.d/rcS << EOF
#!/bin/sh
mkdir -p /tmp
mkdir -p /dev/pts
mkdir -p /dev/shm
/bin/mount -a
if [ -f /etc/hostname ]; then
/bin/hostname -F /etc/hostname
fi
EOF
chmod +x etc/init.d/rcS

cd "${WORK_DIR}"

echo "--- 创建并填充磁盘镜像 ${WORK_DIR}/${IMAGE_NAME} ---"

rm -f "${WORK_DIR}/${IMAGE_NAME}"
echo "正在创建空磁盘镜像 (${IMAGE_SIZE_MB}MB)..."
dd if=/dev/zero of="${WORK_DIR}/${IMAGE_NAME}" bs=1M count=${IMAGE_SIZE_MB} status=progress

echo "正在将磁盘镜像格式化为 ext4..."
mkfs.ext4 -F "${WORK_DIR}/${IMAGE_NAME}"

mkdir -p "${TEMP_MOUNT_DIR}"

echo "正在将loop设备挂载到 ${TEMP_MOUNT_DIR}..."
mount -o loop "${WORK_DIR}/${IMAGE_NAME}" "${TEMP_MOUNT_DIR}"

echo "正在从 ${ROOTFS_DIR} 复制 rootfs 内容到镜像..."
rsync -a --delete "${ROOTFS_DIR}/" "${TEMP_MOUNT_DIR}/"

echo "正在卸载 loop 设备..."
umount "${TEMP_MOUNT_DIR}"
rmdir "${TEMP_MOUNT_DIR}"

echo "--- Rootfs 镜像已创建: ${WORK_DIR}/${IMAGE_NAME} ---"
echo "Rootfs 内容也保留在: ${ROOTFS_DIR}"
echo ""
echo "构建过程完成!"
echo "您现在可以将 ${WORK_DIR}/${IMAGE_NAME} 用于 QEMU 或其他目标系统。"

exit 0

测试

append 后面的 root 不能缺

1
2
3
4
5
6
7
8
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ~/downloads/linux-5.10.191/output/arch/arm/boot/zImage \
-dtb ~/downloads/linux-5.10.191/output/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd ~/downloads/rootfs.ext4

linux 内核裁剪之 uboot

步骤

准备工作

下载地址

1
https://ftp.denx.de/pub/u-boot/u-boot-2023.04.tar.bz2

编译

1
2
make vexpress_ca9x4_defconfig
make -j$(nproc)

测试

qemu 加载 uboot 需要用 elf 的格式

bin 格式的没有入口点,适合烧录到板子上

1
2
3
4
5
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ~/downloads/u-boot-2023.04/output/u-boot \
-nographic

调试

查看变量

1
printenv xxx

手动执行,比如你定义了 bootcmd

1
run bootcmd

重启

1
reset

rt-smart

说明

整理流程很像 linux, 先 load 内核,再加载 rootfs

配置

以 qemu-virt64-aarch64 为例

用户态

工具链需要通过编译用户态程序才能导出,有点奇怪

1
2
3
git clone --depth 1 https://github.com/RT-Thread/userapps.git
cd userapps; source env.sh
cd apps

下载工具链

1
xmake config --arch=aarch64 -y

导出 toolchain 需要先编译

每次修改完应用程序,都需要 xmake 一下

1
xmake

toolchain 在 apps/build/packages

sdk 在apps/build/sdk

1
xmake smart-rootfs --export=all

生成 rootfs,执行以后,在 apps/build/

如果需要把 zig 编译的程序放进去,可以放在这个里面, 实际测试,运行会崩溃

1
xmake smart-rootfs

生成磁盘镜像

1
xmake smart-image -f ext4

内核

toolchain 在 userapps/apps/build/packages 里面,复制出来

1
2
3
4
export RTT_CC="gcc"
export RTT_EXEC_PATH="./toolchain/bin/"
export RTT_CC_PREFIX="aarch64-linux-musleabi-"
export PATH="$RTT_EXEC_PATH:$PATH"

编译内核

1
2
3
git clone --depth 1 https://github.com/RT-Thread/rt-thread
cd rt-thread/bsp/qemu-virt64-aarch64/
scons --menuconfig
1
2
3
4
5
6
RT-Thread Kernel --->
[*] Enable RT-Thread Smart (microkernel on kernel/userland)

RT-Thread online packages --->
system packages --->
[*] lwext4: an excellent choice of ext2/3/4 filesystem for microcontrol
1
2
3
4
5
source ~/.env/env.sh
pkgs --update

scons -c; scons
# scons --dist

运行

1
cp ext4.img ../../../rt-thread/bsp/qemu-virt64-aarch64/

修改 qemu.sh 里面的 sd.bin 为 ext4.img

参考这里最后生成 prebuilt 的部分里面的 run.sh

rt-thread 下,配置 stm32 的 u8g

配置

1
2
3
4
RT-Thread online packages ->
peripheral libraries and drivers --->
U8G2: a monochrome graphic library --->
Version (c-latest)

字体选用 u8g2_font_wqy12_t_gb2312 可以完整显示中文

代码

1
2
3
4
u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_gb2312);
u8g2_DrawUTF8(&u8g2, 1, 18, "我拿起那40米的大砍刀");
u8g2_DrawUTF8(&u8g2, 5, 40, "削你那15米的铅笔");
u8g2_SendBuffer(&u8g2);

rt-thread 下,配置 stm32 的 sd 卡

配置

cubemx

配置 sdio 接口

1
2
3
4
5
选择 SD 4 bit wide bus,生成代码,复制到项目

cubeMX 里面配置的时候,rcc 需要启用 hse,外部晶震,usb 的时钟需要设置为 48MHZ

复制 main.c 里面的时钟配置到 board.c

Kconfig

1
2
3
4
config BSP_USING_SDIO
bool "Enable sd card"
select RT_USING_SDIO
default n

文件系统

1
2
3
RT-Thread Components --->
Device virtual file system --->
[*]Using device virtual file system

配置参数

1
2
3
[*] Enable elm-chan fatfs
elm-chan's FatFs, Generic FAT Filesystem Module --->
(4096) Maximum sector size to be handled.

libc

1
2
3
RT-Thread Components --->
POSIX layer and C standard library --->
[*] Enable libc APIs from toolchain

rt-thread 下,配置 stm32 的 pwm

同一个定时器,多个通道,频率必须一致,占空比可以不同

脉冲经过光耦会失真

代码

pwm_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <rtthread.h>
#include <rtdevice.h>

#define PWM_DEV_NAME "pwm1" // 硬件定时器设备名
#define PWM_CHANNEL1 1 // 通道1
#define PWM_CHANNEL2 2 // 通道2

struct rt_device_pwm *pwm_dev;

static int pwm_demo(int argc, char *argv[])
{
rt_uint32_t period, pulse1, pulse2;

pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
if (pwm_dev == RT_NULL)
{
rt_kprintf("can't find %s device!\n", PWM_DEV_NAME);
return -1;
}

// 2. 设置周期和脉宽(单位:纳秒)
period = 50000; // 20kHz = 1s/20000 = 50,000ns
pulse1 = period * 30 / 100; // 30% 占空比
pulse2 = period * 60 / 100; // 60% 占空比

// 3. 配置 PWM 通道1
rt_pwm_set(pwm_dev, PWM_CHANNEL1, period, pulse1);
rt_pwm_enable(pwm_dev, PWM_CHANNEL1);

// 4. 配置 PWM 通道2
rt_pwm_set(pwm_dev, PWM_CHANNEL2, period, pulse2);
rt_pwm_enable(pwm_dev, PWM_CHANNEL2);

return 0;
}

MSH_CMD_EXPORT(pwm_demo, pwm demo);

测试

逻辑分析仪的 gnd 一定要接,不然数据会不准

rt-thread 下,配置 stm32 的 pulse-encoder

脉冲编码器

配置

cubemx

选一个定时器,设置 Combined Channels 为 Encoder Mode

接线

定时器对应的 channel 接 DT 和 CLK,正反接都可以

代码

pulse_encoder_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <rtthread.h>
#include <rtdevice.h>

#define PULSE_ENCODER_DEV_NAME "pulse3"

static int pulse_encoder_demo(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
rt_device_t pulse_encoder_dev = RT_NULL;

rt_int32_t count;

/* 查找脉冲编码器设备 */
pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME);
if (pulse_encoder_dev == RT_NULL)
{
rt_kprintf("pulse encoder sample run failed! can't find %s device!\n", PULSE_ENCODER_DEV_NAME);
return RT_ERROR;
}

/* 以只读方式打开设备 */
ret = rt_device_open(pulse_encoder_dev, RT_DEVICE_OFLAG_RDONLY);
if (ret != RT_EOK)
{
rt_kprintf("open %s device failed!\n", PULSE_ENCODER_DEV_NAME);
return ret;
}

for(rt_uint32_t i = 0; i <= 10000; i++)
{
rt_thread_mdelay(500);

rt_device_read(pulse_encoder_dev, 0, &count, 1);
rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_CLEAR_COUNT, RT_NULL);
rt_kprintf("get count %d\n",count);
}

rt_device_close(pulse_encoder_dev);
return ret;
}

MSH_CMD_EXPORT(pulse_encoder_demo, pulse_encoder_demo);

注意

同一个定时器,如果某通道发了脉冲,其他通道不能再用来读取脉冲编码器了,会冲突

rt-thread 下,配置 stm32 的 mqtt

目前使用的是 kawaii-mqtt

服务器

1
docker run --rm -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx:v3.1.1

说明:

1
2
3
4
5
6
1883 MQTT 端口
8883 MQTT / SSL 端口
8083 MQTT / WebSocket 端口
8084 MQTT / WebSocket / SSL 端口
8080 HTTP 管理 API 端口
18083 Web 仪表板端口

管理后台

1
2
3
http://127.0.0.1:18083
admin
public

rtthread

1
2
3
4
5
6
7
8
RT-Thread online packages >
IoT - internet of things
[*] kawaii-mqtt: a mqtt client based on the socket API, support QoS2, mbedtls
[*] using SAL

RT-Thread Components >
Network > Socket abstraction layer
[*] Enable BSD socket operated by file system API

代码

demo.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# -*-coding:utf-8-*-


# Import paho-mqtt Client class:
import paho.mqtt.client as mqtt
import time

unacked_sub = [] # a list for unacknowledged subscription

# Define the callback to handle CONNACK from the broker, if the connection created normal, the value of rc is 0
def on_connect(client, userdata, flags, rc):
print("Connection returned with result code:" + str(rc))


# Define the callback to hande publish from broker, here we simply print out the topic and payload of the received message
def on_message(client, userdata, msg):
print("Received message, topic: " + msg.topic + " payload: " + str(msg.payload))


# Callback handles disconnection, print the rc value
def on_disconnect(client, userdata, rc):
print("Disconnection returned result: " + str(rc))


# Remove the message id from the list for unacknowledged subscription
def on_subscribe(client, userdata, mid, granted_qos):
unacked_sub.remove(mid)


# Create an instance of `Client`
client = mqtt.Client()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.on_subscribe = on_subscribe

# Connect to broker
# connect() is blocking, it returns when the connection is successful or failed. If you want client connects in a non-blocking way, you may use connect_async() instead
client.connect("127.0.0.1", 1883, 60)

client.loop_start()

# Subscribe to a single topic
result, mid = client.subscribe("demo_topic", 0)
unacked_sub.append(mid)
# Subscribe to multiple topics
result, mid = client.subscribe([("temperature", 0), ("humidity", 0)])
unacked_sub.append(mid)

while len(unacked_sub) != 0:
time.sleep(1)

client.publish("demo_topic", payload="Hello world!")
client.publish("temperature", payload="24.0")
client.publish("humidity", payload="65%")

# Disconnection
time.sleep(5)
client.loop_stop()
client.disconnect()

demo_mqtt.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdint.h>

#include "mqttclient.h"


static void on_message_receive(void* client, message_data_t* msg)
{
(void) client;
rt_kprintf("-----------------------------------------------------------------------------------");
rt_kprintf("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload);
rt_kprintf("-----------------------------------------------------------------------------------");
}


static int mqtt_say_hello(mqtt_client_t *client)
{
mqtt_message_t msg;
memset(&msg, 0, sizeof(msg));

msg.qos = 0;
msg.payload = (void *) "payload to sent";

return mqtt_publish(client, "demo_topic", &msg);
}


int mqtt_demo(void)
{
mqtt_client_t *client = NULL;

mqtt_log_init();

client = mqtt_lease();

mqtt_set_host(client, "192.168.88.234");
mqtt_set_port(client, "1883");
mqtt_set_clean_session(client, 1);
mqtt_set_keep_alive_interval(client, 10);

mqtt_connect(client);
mqtt_subscribe(client, "demo_topic", QOS0, on_message_receive);

while (1) {
mqtt_say_hello(client);
mqtt_sleep_ms(4 * 1000);
}
}

MSH_CMD_EXPORT(mqtt_demo, mqtt_demo);

注意

esp8266 可以连

LAN8720A 很稳定

目前 w5500 各种连不上

rt-thread 下,配置 stm32 的 freemodbus

freemodbus

1
2
RT-Thread online packages ->
[*] FreeModbus: Modbus master and slave stack

rtu 的话,用 freemodbus

1
例子里面只有 rtu 的 master 和 slave 模式

代码

freemodbus_rtu_master_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <rtthread.h>
#include <mb.h>
#include <mb_m.h>

#define MB_POLL_THREAD_PRIORITY 10
#define MB_SEND_THREAD_PRIORITY RT_THREAD_PRIORITY_MAX - 1

static void rtu_master_demo_thread(void *parameter) {
eMBMasterReqErrCode error_code = MB_MRE_NO_ERR;
rt_uint16_t error_count = 0;
USHORT data[2] = {0};

while (1){
data[0] = (USHORT)(rt_tick_get() / 10);
data[1] = (USHORT)(rt_tick_get() % 10);

error_code = eMBMasterReqWriteMultipleHoldingRegister(
1, /* salve address */
2, /* register start address */
2, /* register total number */
data, /* data to be written */
RT_WAITING_FOREVER); /* timeout */

/* Record the number of errors */
if (error_code != MB_MRE_NO_ERR)
{
error_count++;
}
}
}

static void modbus_master_poll(void *parameter){
// uart5
eMBMasterInit(MB_RTU, 5, 115200, MB_PAR_EVEN);
eMBMasterEnable();

while (1){
eMBMasterPoll();
rt_thread_mdelay(500);
}
}

int freemodbus_rtu_master_demo(){
static rt_uint8_t is_init = 0;
rt_thread_t tid1 = RT_NULL, tid2 = RT_NULL;

if (is_init > 0){
rt_kprintf("libmodbus_rtu_master is running\n");
return -RT_ERROR;
}
tid1 = rt_thread_create("modbus_master_poll", modbus_master_poll, RT_NULL, 512, MB_POLL_THREAD_PRIORITY, 10);
if (tid1 != RT_NULL){
rt_thread_startup(tid1);
}
else{
goto __exit;
}

tid2 = rt_thread_create("rtu_master_demo_thread", rtu_master_demo_thread, RT_NULL, 512, MB_SEND_THREAD_PRIORITY, 10);
if (tid2 != RT_NULL){
rt_thread_startup(tid2);
}
else{
goto __exit;
}

is_init = 1;
return RT_EOK;

__exit:
if (tid1) rt_thread_delete(tid1);
if (tid2) rt_thread_delete(tid2);

return -RT_ERROR;
}

MSH_CMD_EXPORT(freemodbus_rtu_master_demo, freemodbus_rtu_master_demo)

freemodbus_rtu_slave_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <rtthread.h>
#include <mb.h>
#include <mb_m.h>
#include <user_mb_app.h>

#define MB_POLL_THREAD_PRIORITY 10
#define MB_SEND_THREAD_PRIORITY RT_THREAD_PRIORITY_MAX - 1

extern USHORT usSRegHoldBuf[S_REG_HOLDING_NREGS];

// master来读取slave的数据
static void rtu_slave_demo_thread(void *parameter)
{
USHORT* usRegHoldingBuf;
usRegHoldingBuf = usSRegHoldBuf;
rt_base_t level;

while (1){
level = rt_hw_interrupt_disable();

usRegHoldingBuf[3] = (USHORT)(rt_tick_get() / 100);

rt_hw_interrupt_enable(level);

rt_thread_mdelay(1000);
}
}

static void modbus_slave_poll(void *parameter)
{
// 从机地址1, uart5
eMBInit(MB_RTU, 1, 5, 115200, MB_PAR_EVEN);
eMBEnable();
while (1)
{
eMBPoll();
rt_thread_mdelay(200);
}
}
int freemodbus_rtu_slave_demo(){
static rt_uint8_t is_init = 0;
rt_thread_t tid1 = RT_NULL, tid2 = RT_NULL;

if (is_init > 0){
rt_kprintf("sample is running\n");
return -RT_ERROR;
}
tid1 = rt_thread_create("modbus_slave_poll", modbus_slave_poll, RT_NULL, 512, MB_POLL_THREAD_PRIORITY, 10);
if (tid1 != RT_NULL){
rt_thread_startup(tid1);
}
else{
goto __exit;
}

tid2 = rt_thread_create("rtu_slave_demo_thread", rtu_slave_demo_thread, RT_NULL, 512, MB_SEND_THREAD_PRIORITY, 10);
if (tid2 != RT_NULL){
rt_thread_startup(tid2);
}
else{
goto __exit;
}

is_init = 1;
return RT_EOK;

__exit:
if (tid1) rt_thread_delete(tid1);

if (tid2) rt_thread_delete(tid2);

return -RT_ERROR;
}

MSH_CMD_EXPORT(freemodbus_rtu_slave_demo, freemodbus_rtu_slave_demo)

rt-thread 下,配置 stm32 的 libmodbus

libmodbus

1
2
tcp 的 master 和 slave 都有
rtu 只有 master

配置

1
2
RT-Thread online packages > IoT - internet of things
[*] libmodbus: A Modbus library for RT-Thread

rtu 模式,如果开发板接 232 之类的口,对应看到原理图以后,设备名为 /dev/uartx

从 demo 里面看,似乎对于 slave 的支持很一般,解码都需要自己做

代码

libmodbus_rtu_master_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <rtthread.h>
#include <rtdevice.h>
#include <modbus.h>

static void rtu_master_demo_thread(void *param){
uint16_t tab_reg[64] = {0};
modbus_t* ctx = RT_NULL;

ctx = modbus_new_rtu("/dev/uart5", 115200, 'N', 8, 1);
modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS232);
modbus_set_slave(ctx, 3);
modbus_connect(ctx);
modbus_set_response_timeout(ctx, 0, 1000000);
int num = 0;
while (1)
{
memset(tab_reg, 0, 64 * 2);
int regs = modbus_read_registers(ctx, 0, 20, tab_reg);
printf("-------------------------------------------\n");
printf("[%4d][read num = %d]", num, regs);
num++;
int i;
for (i = 0; i < 20; i++)
{
printf("<%#x>", tab_reg[i]);
}
printf("\n");
printf("-------------------------------------------\n");
rt_thread_mdelay(2000);
}

modbus_close(ctx);
modbus_free(ctx);
}

int libmodbus_rtu_master_demo(){
rt_thread_t tid;
tid = rt_thread_create("rtu_master_demo",
rtu_master_demo_thread, RT_NULL,
2048,
12, 10);
if (tid != RT_NULL) rt_thread_startup(tid);
return RT_EOK;
}

MSH_CMD_EXPORT(libmodbus_rtu_master_demo, libmodbus_rtu_master_demo)

libmodbus_tcp_master_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <rtthread.h>
#include <rtdevice.h>
#include <modbus.h>
#include <sal_socket.h>

static void tcp_master_demo_thread(void *param)
{
uint16_t tab_reg[64] = {0};
modbus_t *ctx = RT_NULL;

ctx = modbus_new_tcp("192.168.88.61", 502, AF_INET);
modbus_set_slave(ctx, 3);
modbus_set_response_timeout(ctx, 0, 1000000);
_modbus_tcp_start:
if(modbus_connect(ctx) < 0) goto _modbus_tcp_restart;

int num = 0;
while (1)
{
memset(tab_reg, 0, 64 * 2);
int regs = modbus_read_registers(ctx, 0, 20, tab_reg);
if(regs < 0) goto _modbus_tcp_restart;

printf("-------------------------------------------\n");
printf("[%4d][read num = %d]", num, regs);
num++;
int i;
for (i = 0; i < 20; i++)
{
printf("<%#x>", tab_reg[i]);
}
printf("\n");
printf("-------------------------------------------\n");
rt_thread_mdelay(1000);
}

_modbus_tcp_restart:
modbus_close(ctx);
rt_thread_mdelay(2000);
goto _modbus_tcp_start;

modbus_free(ctx);
}

int libmodbus_tcp_master_demo(){
rt_thread_t tid;
tid = rt_thread_create("tcp_master_demo",
tcp_master_demo_thread, RT_NULL,
2048,
12, 10);
if (tid != RT_NULL) rt_thread_startup(tid);
return RT_EOK;
}

MSH_CMD_EXPORT(libmodbus_tcp_master_demo, libmodbus_tcp_master_demo)

libmodbus_tcp_slave_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <rtthread.h>
#include <rtdevice.h>
#include <modbus.h>
#include <sal_socket.h>

#define MAX_CLIENT_NUM 3
#define CLIENT_TIMEOUT 10

typedef struct
{
int fd;
rt_tick_t tick_timeout;
}client_session_t;

static void tcp_slave_demo_thread(void *param)
{
int server_fd = -1;
modbus_t *ctx = NULL;
modbus_mapping_t *mb_mapping = NULL;
client_session_t client_session[MAX_CLIENT_NUM];

for (int i = 0; i < MAX_CLIENT_NUM; i++)
{
client_session[i].fd = -1;
client_session[i].tick_timeout = rt_tick_get() + rt_tick_from_millisecond(CLIENT_TIMEOUT * 1000);
}

int max_fd = -1;
fd_set readset;
int rc;
struct timeval select_timeout;
select_timeout.tv_sec = 1;
select_timeout.tv_usec = 0;

ctx = modbus_new_tcp(RT_NULL, 1502, AF_INET);
RT_ASSERT(ctx != RT_NULL);
mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 0,
MODBUS_MAX_READ_REGISTERS, 0);
RT_ASSERT(mb_mapping != RT_NULL);

_mbtcp_start:
server_fd = modbus_tcp_listen(ctx, 1);
if (server_fd < 0)
goto _mbtcp_restart;

while (1)
{
max_fd = -1;
FD_ZERO(&readset);
FD_SET(server_fd, &readset);

if(max_fd < server_fd)
max_fd = server_fd;

for (int i = 0; i < MAX_CLIENT_NUM; i++)
{
if(client_session[i].fd >= 0)
{
FD_SET(client_session[i].fd, &readset);
if(max_fd < client_session[i].fd)
max_fd = client_session[i].fd;
}
}

rc = select(max_fd + 1, &readset, RT_NULL, RT_NULL, &select_timeout);
if(rc < 0)
{
goto _mbtcp_restart;
}
else if(rc > 0)
{
if(FD_ISSET(server_fd, &readset))
{
int client_sock_fd = modbus_tcp_accept(ctx, &server_fd);
if(client_sock_fd >= 0)
{
int index = -1;
for (int i = 0; i < MAX_CLIENT_NUM; i++)
{
if(client_session[i].fd < 0)
{
index = i;
break;
}
}
if(index >= 0)
{
client_session[index].fd = client_sock_fd;
client_session[index].tick_timeout = rt_tick_get() + rt_tick_from_millisecond(CLIENT_TIMEOUT * 1000);
}
else
{
close(client_sock_fd);
}
}
}

for (int i = 0; i < MAX_CLIENT_NUM; i++)
{
if(client_session[i].fd >= 0)
{
if(FD_ISSET(client_session[i].fd, &readset))
{
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
modbus_set_socket(ctx, client_session[i].fd);

rc = modbus_receive(ctx, query);
if (rc > 0)
{
rc = modbus_reply(ctx, query, rc, mb_mapping);
if(rc < 0)
{
close(client_session[i].fd);
client_session[i].fd = -1;
}
else
{
client_session[i].tick_timeout = rt_tick_get() + rt_tick_from_millisecond(CLIENT_TIMEOUT * 1000);
}
}
else
{
close(client_session[i].fd);
client_session[i].fd = -1;
}
}
}
}
}

// 客户端超时未收到数据断开
for(int i =0;i<MAX_CLIENT_NUM;i++)
{
if(client_session[i].fd >= 0)
{
//超时
if((rt_tick_get() - client_session[i].tick_timeout) < (RT_TICK_MAX / 2))
{
close(client_session[i].fd);
client_session[i].fd = -1;
}
}
}
}

_mbtcp_restart:
if(server_fd >= 0)
{
close(server_fd);
server_fd = -1;
}

for(int i =0;i<MAX_CLIENT_NUM;i++)
{
if(client_session[i].fd >= 0)
{
close(client_session[i].fd);
client_session[i].fd = -1;
}
}

rt_thread_mdelay(5000);
goto _mbtcp_start;

modbus_free(ctx);
}

int libmodbus_tcp_slave_demo(){
rt_thread_t tid;
tid = rt_thread_create("tcp_slave_demo",
tcp_slave_demo_thread, RT_NULL,
2048,
12, 10);
if (tid != RT_NULL) rt_thread_startup(tid);
return RT_EOK;
}

MSH_CMD_EXPORT(libmodbus_tcp_slave_demo, libmodbus_tcp_slave_demo)