使用 ftrace 观测 GPIO 从用户调用到驱动的完整轨迹需要跟踪系统调用、VFS 层、设备驱动等多个层次。以下是详细的观测方案:

1. 准备环境和测试程序

1.1 简单的 GPIO 测试程序

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
// trace_gpio_test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>

int main() {
int fd;
void *gpio_map;

printf("GPIO mmap 测试开始...\n");

// 打开 gpiomem 设备
fd = open("/dev/gpiomem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open /dev/gpiomem failed");
return -1;
}
printf("GPIO设备打开成功: fd=%d\n", fd);

// 执行 mmap
gpio_map = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (gpio_map == MAP_FAILED) {
perror("mmap failed");
close(fd);
return -1;
}
printf("GPIO映射成功: %p\n", gpio_map);

// 简单的 GPIO 访问测试
volatile uint32_t *gpio = (volatile uint32_t*)gpio_map;
uint32_t value = gpio[0]; // 读取第一个寄存器
printf("GPIO寄存器值: 0x%08x\n", value);

// 保持一段时间以便观察
printf("按回车键继续...\n");
getchar();

// 清理
munmap(gpio_map, 4096);
close(fd);
printf("测试完成\n");

return 0;
}

编译运行:

1
2
3
cd ~/code
gcc trace_gpio_test.c -o trace_gpio
./trace_gpio

2. 配置 ftrace 跟踪点

2.1 实际可用的跟踪脚本

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
#!/bin/bash
# trace_gpio_mmap.sh

echo "=== GPIO mmap 完整轨迹跟踪 ==="

# 挂载 debugfs
mount -t debugfs nodev /sys/kernel/debug/tracing 2>/dev/null
cd /sys/kernel/debug/tracing

# 1. 重置跟踪器
echo 0 | sudo tee tracing_on > /dev/null
echo | sudo tee trace > /dev/null
echo | sudo tee set_ftrace_filter > /dev/null
echo | sudo tee set_graph_function > /dev/null

# 2. 设置 function_graph 跟踪器
echo function_graph | sudo tee current_tracer > /dev/null

# 3. 设置已验证的函数名(根据 available_filter_functions)
echo "__arm64_sys_mmap" | sudo tee -a set_graph_function > /dev/null
echo "ksys_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null
echo "vm_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null
echo "do_mmap" | sudo tee -a set_graph_function > /dev/null
echo "mmap_region" | sudo tee -a set_graph_function > /dev/null
echo "__mmap_region" | sudo tee -a set_graph_function > /dev/null
echo "rpi_gpiomem_mmap" | sudo tee -a set_graph_function > /dev/null
echo "rpi_gpiomem_open" | sudo tee -a set_graph_function > /dev/null

# 4. 添加 VMA 相关函数
echo "vm_area_alloc" | sudo tee -a set_graph_function > /dev/null
echo "vma_merge_new_range" | sudo tee -a set_graph_function > /dev/null
echo "vma_link_file" | sudo tee -a set_graph_function > /dev/null
echo "vma_set_page_prot" | sudo tee -a set_graph_function > /dev/null

# 5. 添加内存映射核心函数
echo "remap_pfn_range" | sudo tee -a set_graph_function > /dev/null
echo "remap_pfn_range_internal" | sudo tee -a set_graph_function > /dev/null
echo "__pte_offset_map_lock" | sudo tee -a set_graph_function > /dev/null

# 6. 配置跟踪选项
echo 1 | sudo tee options/func_stack_trace > /dev/null
echo 1 | sudo tee options/sleep-time > /dev/null
echo 1 | sudo tee options/graph-time > /dev/null

# 7. 开始跟踪
echo 1 | sudo tee tracing_on > /dev/null

# 8. 运行测试程序
echo "运行测试程序..."
/home/pi/code/trace_gpio &

# 9. 等待并停止跟踪
sleep 3
echo 0 | sudo tee tracing_on > /dev/null

# 10. 保存跟踪结果
sudo cat trace > /tmp/gpio_mmap_complete_trace.log
echo "跟踪结果保存到: /tmp/gpio_mmap_complete_trace.log"

# 11. 显示关键部分
echo "=== GPIO 驱动调用路径 ==="
sudo cat trace | grep -A30 -B5 "rpi_gpiomem" | head -80

3. 详细的函数跟踪配置

3.1 完整的已验证函数列表

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
#!/bin/bash
# trace_gpio_detailed.sh

cd /sys/kernel/debug/tracing

# 清除之前的设置
echo 0 | sudo tee tracing_on > /dev/null
echo nop | sudo tee current_tracer > /dev/null
echo | sudo tee set_ftrace_filter > /dev/null
echo | sudo tee set_graph_function > /dev/null

# 设置 function_graph
echo function_graph | sudo tee current_tracer > /dev/null

# 系统调用入口
echo "__arm64_sys_mmap" | sudo tee -a set_graph_function > /dev/null
echo "ksys_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null

# 内存管理核心函数
echo "vm_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null
echo "do_mmap" | sudo tee -a set_graph_function > /dev/null
echo "mmap_region" | sudo tee -a set_graph_function > /dev/null
echo "__mmap_region" | sudo tee -a set_graph_function > /dev/null

# VMA 操作函数
echo "vm_area_alloc" | sudo tee -a set_graph_function > /dev/null
echo "vma_merge_new_range" | sudo tee -a set_graph_function > /dev/null
echo "vma_link_file" | sudo tee -a set_graph_function > /dev/null
echo "vma_set_page_prot" | sudo tee -a set_graph_function > /dev/null

# 文件操作相关
echo "fget" | sudo tee -a set_graph_function > /dev/null
echo "fput" | sudo tee -a set_graph_function > /dev/null

# 驱动特定函数
echo "rpi_gpiomem_mmap" | sudo tee -a set_graph_function > /dev/null
echo "rpi_gpiomem_open" | sudo tee -a set_graph_function > /dev/null

# 页表操作函数
echo "remap_pfn_range" | sudo tee -a set_graph_function > /dev/null
echo "remap_pfn_range_internal" | sudo tee -a set_graph_function > /dev/null
echo "__pte_offset_map_lock" | sudo tee -a set_graph_function > /dev/null

# 安全相关函数
echo "security_mmap_file" | sudo tee -a set_graph_function > /dev/null
echo "security_mmap_addr" | sudo tee -a set_graph_function > /dev/null
echo "cap_mmap_file" | sudo tee -a set_graph_function > /dev/null
echo "cap_mmap_addr" | sudo tee -a set_graph_function > /dev/null

# 设置跟踪选项
echo 1 | sudo tee options/func_stack_trace > /dev/null
echo 1 | sudo tee options/graph-time > /dev/null

# 开始跟踪
echo 1 | sudo tee tracing_on > /dev/null
echo "跟踪开始,运行测试程序..."
/home/pi/code/trace_gpio &
sleep 3
echo 0 | sudo tee tracing_on > /dev/null

# 显示结果
sudo cat trace | head -100

4. 使用 trace-cmd 进行更精细控制

4.1 trace-cmd 配置(针对 GPIO)

1
2
3
4
5
6
# 安装 trace-cmd
sudo apt update
sudo apt install trace-cmd

# 验证安装
trace-cmd --version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# trace_gpio_tracecmd.sh

echo "使用 trace-cmd 跟踪 GPIO mmap..."

# 记录跟踪数据(使用 sudo)
timeout 10 sudo trace-cmd record \
-e syscalls:sys_enter_mmap \
-e syscalls:sys_exit_mmap \
-p function_graph \
-g __arm64_sys_mmap \
-g rpi_gpiomem_mmap \
-g remap_pfn_range \
-F /home/pi/code/trace_gpio

# 生成报告(旧版本命令)
echo "=== GPIO 相关事件 ==="
sudo trace-cmd report | grep -i gpio

echo -e "\n=== mmap 系统调用 ==="
sudo trace-cmd report | grep -i mmap

echo -e "\n=== 函数图分析 ==="
sudo trace-cmd report | grep -A10 -B5 "rpi_gpiomem_mmap\|__arm64_sys_mmap"

或者使用更简单的方法:

1
2
sudo trace-cmd record -e 'syscalls:*mmap*' -p function -F /home/pi/code/trace_gpio
sudo trace-cmd report

5. 分析 ftrace 输出日志

5.1 关键路径识别方法

根据 /tmp/gpio_mmap_complete_trace.log 分析 GPIO mmap 完整轨迹:

1. 识别系统调用入口

1
2
# 查找 mmap 系统调用开始
grep -n "__arm64_sys_mmap\|ksys_mmap" /tmp/gpio_mmap_complete_trace.log

2. 跟踪内存管理调用链

1
2
# 查看内存管理核心函数
grep -A10 -B5 "do_mmap\|mmap_region\|__mmap_region" /tmp/gpio_mmap_complete_trace.log

3. 定位驱动特定函数

1
2
# 查找 GPIO 驱动函数
grep -A20 -B10 "rpi_gpiomem_mmap\|rpi_gpiomem_open" /tmp/gpio_mmap_complete_trace.log

5.2 性能热点分析

分析页表操作频率:

1
2
3
# 统计 __pte_offset_map_lock 调用次数和总耗时
grep "__pte_offset_map_lock" /tmp/gpio_mmap_complete_trace.log | wc -l
grep "__pte_offset_map_lock" /tmp/gpio_mmap_complete_trace.log | awk '{sum += $3} END {print "总耗时:", sum, "us"}'

识别内存分配瓶颈:

1
2
# 查看 VMA 分配耗时
grep -A5 "vm_area_alloc\|kmem_cache_alloc" /tmp/gpio_mmap_complete_trace.log

5.3 调用深度分析

重建完整调用路径:

1
2
# 提取关键函数的时间线
awk '/__arm64_sys_mmap/,/rpi_gpiomem_mmap/' /tmp/gpio_mmap_complete_trace.log | head -50

验证驱动映射成功:

1
2
# 检查 remap_pfn_range 是否执行
grep -A10 "remap_pfn_range" /tmp/gpio_mmap_complete_trace.log
## mmap分析

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
tail -100 /tmp/gpio_mmap_complete_trace.log
3) 0.593 us | _raw_spin_lock();
3) 3.037 us | }
3) | __pte_offset_map_lock() {
3) 0.685 us | __rcu_read_lock();
3) 0.685 us | _raw_spin_lock();
3) 3.019 us | }
3) | __arm64_sys_mmap() {
3) | ksys_mmap_pgoff() {
3) | fget() {
3) 0.574 us | __rcu_read_lock();
3) 0.667 us | __rcu_read_unlock();
3) 3.056 us | }
3) | vm_mmap_pgoff() {
3) | security_mmap_file() {
3) 0.556 us | cap_mmap_file();
3) 1.870 us | }
3) 0.722 us | down_write_killable();
3) | do_mmap() {
3) | __get_unmapped_area() {
3) | mm_get_unmapped_area_vmflags() {
3) | generic_get_unmapped_area_topdown() {
3) 1.352 us | vm_unmapped_area();
3) 2.574 us | }
3) 3.889 us | }
3) | security_mmap_addr() {
3) 0.593 us | cap_mmap_addr();
3) 1.852 us | }
3) 7.463 us | }
3) | memfd_file_seals_ptr() {
3) 0.556 us | shmem_mapping();
3) 1.834 us | }
3) 0.685 us | path_noexec();
3) | mmap_region() {
3) | __mmap_region() {
3) 0.666 us | may_expand_vm();
3) 0.648 us | vms_clean_up_area();
3) | vma_merge_new_range() {
3) 0.592 us | can_vma_merge_right();
3) 1.870 us | }
3) | vm_area_alloc() {
3) 0.667 us | kmem_cache_alloc_noprof();
3) 0.685 us | kmem_cache_alloc_noprof();
3) 0.574 us | __init_rwsem();
3) 4.463 us | }
3) 0.574 us | vm_get_page_prot();
3) 0.629 us | kmem_cache_alloc_noprof();
3) | ext4_file_mmap() {
3) | touch_atime() {
3) | atime_needs_update() {
3) 0.574 us | make_vfsuid();
3) 0.555 us | make_vfsgid();
3) 3.000 us | }
3) 4.297 us | }
3) 5.408 us | }
3) 0.611 us | down_write();
3) 0.703 us | up_write();
3) | call_rcu() {
3) | __call_rcu_common.constprop.0() {
3) 0.574 us | rcu_segcblist_enqueue();
3) 1.870 us | }
3) 3.186 us | }
3) | vma_link_file() {
3) 0.611 us | down_write();
3) 0.796 us | vma_interval_tree_insert();
3) 0.593 us | up_write();
3) 4.481 us | }
3) 0.574 us | perf_event_mmap();
3) 0.574 us | vms_complete_munmap_vmas();
3) | vma_set_page_prot() {
3) 0.667 us | vm_get_page_prot();
3) 0.648 us | vma_wants_writenotify();
3) 2.907 us | }
3) + 37.925 us | }
3) + 39.185 us | }
3) + 52.093 us | }
3) 0.574 us | up_write();
3) + 58.315 us | }
3) 0.574 us | fput();
3) + 65.111 us | }
3) + 66.555 us | }
3) | __pte_offset_map_lock() {
3) 0.667 us | __rcu_read_lock();
3) 0.686 us | _raw_spin_lock();
3) 2.982 us | }
3) | __pte_offset_map_lock() {
3) 0.685 us | __rcu_read_lock();
3) 0.666 us | _raw_spin_lock();
3) 2.963 us | }
3) | __pte_offset_map_lock() {
3) 0.574 us | __rcu_read_lock();
3) 0.593 us | _raw_spin_lock();
3) 3.074 us | }
3) | __pte_offset_map_lock() {
3) 0.575 us | __rcu_read_lock();
3) 0.592 us | _raw_spin_lock();
3) 3.093 us | }
3) | __pte_offset_map_lock() {
3) 0.575 us | __rcu_read_lock();
3) 0.574 us | _raw_spin_lock();
3) 2.963 us | }

当用户调用mmap()的时候,内核会进行如下处理 1. 在进程的虚拟空间查找一块VMA 2. 将这块VMA进行映射 3. 如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它。 4. 将这个VMA插入进程的VMA链表中

从 ftrace 输出可以清晰地验证 mmap 处理的四个步骤:

1. 查找 VMA (虚拟内存区域)

1
2
3
4
5
6
7
8
9
# 在进程虚拟空间查找空闲区域
__get_unmapped_area() {
mm_get_unmapped_area_vmflags() {
generic_get_unmapped_area_topdown() {
vm_unmapped_area(); # 查找合适的虚拟地址范围
}
}
}
security_mmap_addr(); # 安全检查

2. 分配和设置 VMA

1
2
3
4
5
6
7
8
vm_area_alloc() {  # 分配 VMA 结构体
kmem_cache_alloc_noprof(); # 从 slab 分配器分配内存
}
vm_get_page_prot(); # 设置页面保护权限
vma_set_page_prot() {
vm_get_page_prot(); # 获取页面保护标志
vma_wants_writenotify(); # 设置写通知
}

3. 调用驱动/文件系统的 mmap 操作

1
2
3
4
5
6
7
8
9
ext4_file_mmap() {  # 文件系统的 mmap 操作
touch_atime() { # 更新访问时间
atime_needs_update() {
make_vfsuid(); # 权限检查
make_vfsgid();
}
}
}
# 注意:这里调用的是 ext4 的 mmap,如果是 GPIO 驱动应该显示 rpi_gpiomem_mmap

4. 将 VMA 插入进程链表

1
2
3
4
5
vma_link_file() {  # 将 VMA 链接到进程的 VMA 链表
down_write(); # 获取写锁
vma_interval_tree_insert(); # 插入到区间树中
up_write(); # 释放锁
}

关键证据总结:

  1. VMA 查找__get_unmapped_area()vm_unmapped_area() 负责在进程地址空间找到合适区域
  2. VMA 分配vm_area_alloc() 明确分配了 VMA 结构体
  3. 驱动 mmap 调用ext4_file_mmap() 显示了文件操作的具体 mmap 实现(在你的 GPIO 案例中应该是 rpi_gpiomem_mmap
  4. VMA 链表插入vma_link_file()vma_interval_tree_insert() 完成了 VMA 到进程数据结构的链接

这个 trace 完美印证了 mmap 的四个处理步骤。

实验名称:树莓派PWM信号生成与示波器分析

实验目标

  1. 通过Python编程,让树莓派生成一个可调占空比的PWM信号。
  2. 用示波器捕捉并测量该PWM信号的频率和占空比,验证软件控制与硬件输出的一致性。

所需设备

  1. 硬件
    • 树莓派 (任何型号,已安装系统)
    • MacBook
    • 数字示波器
    • 面包板、LED灯、220Ω电阻、杜邦线若干
    • USB转TTL串口模块 (可选,用于串口调试)
  2. 软件
    • MacBook上的终端 (用于SSH或串口连接)
    • 树莓派上的Python3及 RPi.GPIO

具体步骤

第一步:硬件连接

  1. 树莓派GPIO连接
    • 将LED的正极(长脚)通过一个220Ω的限流电阻,连接到树莓派的 GPIO 18 (物理引脚12)。这是因为GPIO 18支持硬件PWM。
    • 将LED的负极(短脚)连接到树莓派的 GND (物理引脚6)。
  2. 示波器连接
    • 将示波器探头的尖端钩在 GPIO 18 引脚上。
    • 将示波器探头的接地夹夹在树莓派的 GND 引脚上。
  3. MacBook连接树莓派
    • 方法A (推荐,使用SSH):确保MacBook和树莓派在同一个Wi-Fi下,在Mac终端输入:ssh pi@<你的树莓派IP地址>,然后输入密码。
    • 方法B (使用串口):通过USB转TTL模块连接树莓派的UART引脚,在Mac上用 screen 命令连接。

连接示意图:

1
2
3
树莓派 GPIO 18 ---[220Ω电阻]--- LED(+) --- LED(-) --- GND
|
+--- (连接至示波器探头)

1. 命令行工具:pinout

这是最直接、最推荐的方法。

在你的树莓派终端中,直接输入:

1
pinout
这个命令会返回一个清晰的ASCII艺术图,明确标出了: - 物理引脚编号 (1, 2, 3...) - BCM GPIO编号 (GPIO 2, GPIO 3...),这是 RPi.GPIO 库默认使用的编号。 - 电源引脚 (3.3V, 5V, GND) - 特殊功能引脚 (例如,我们实验要用到的GPIO 18)

示例输出(部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
   3V3  (1) ◉◉ (2)  5V
GPIO2 (3) ◉◉ (4) 5V
GPIO3 (5) ◉◉ (6) GND
GPIO4 (7) ◉◉ (8) GPIO14
GND (9) ◉◉ (10) GPIO15
GPIO17 (11) ◉◉ (12) GPIO18 <-- 我们要用的引脚!
GPIO27 (13) ◉◉ (14) GND
GPIO22 (15) ◉◉ (16) GPIO23
3V3 (17) ◉◉ (18) GPIO24
GPIO10 (19) ◉◉ (20) GND
GPIO9 (21) ◉◉ (22) GPIO25
GPIO11 (23) ◉◉ (24) GPIO8
GND (25) ◉◉ (26) GPIO7

从图中可以看到,物理引脚12 对应的就是 BCM GPIO 18

2. GPIO库的Python命令

如果你已经在Python环境中,可以这样验证:

  1. 在树莓派终端输入 python3 进入Python交互环境。
  2. 依次输入以下命令:
    1
    2
    3
    4
    5
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM) # 设置为BCM编号模式
    # 将GPIO 18设置为输出模式,这会初始化该引脚
    GPIO.setup(18, GPIO.OUT)
    print("GPIO 18 设置成功!")
    如果没有报错,说明GPIO 18是可用的。

第二步:创建并运行Python脚本

  1. 在树莓派上,创建一个Python文件,例如 pwm_test.py

    1
    vi pwm_test.py

  2. 将以下代码复制进去:

    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
    #!/usr/bin/env python3
    import RPi.GPIO as GPIO
    import time
    import atexit

    PWM_FREQUENCY = 1000 # 频率
    PWM_DUTY_CYCLE = 50 # 占空比
    # 全局变量跟踪PWM状态
    pwm_active = False
    pwm_instance = None

    def cleanup_resources():
    """在程序退出时清理资源"""
    global pwm_active, pwm_instance
    if pwm_active and pwm_instance:
    try:
    pwm_instance.stop()
    pwm_active = False
    pwm_instance = None
    except:
    pass
    try:
    GPIO.cleanup()
    except:
    pass

    # 注册退出处理函数
    atexit.register(cleanup_resources)

    try:
    # 设置GPIO
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(18, GPIO.OUT)

    # 创建并启动PWM
    pwm_instance = GPIO.PWM(18, PWM_FREQUENCY)
    pwm_instance.start(PWM_DUTY_CYCLE)
    pwm_active = True

    print(f"PWM已启动,频率{PWM_FREQUENCY}Hz,占空比{PWM_DUTY_CYCLE}%。按 Ctrl+C 停止程序。")

    # 主循环
    while True:
    time.sleep(1)

    except KeyboardInterrupt:
    print("\n程序被用户中断")

    finally:
    cleanup_resources()
    print("资源清理完成")

  3. 保存并退出 (在nano中按 Ctrl+X, 然后按 Y, 最后按 Enter)。

  4. 运行脚本 (需要sudo权限来访问GPIO):

    1
    sudo python3 pwm_test.py
    ### 硬件PWM

方法1:从源码编译安装pigpio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装编译依赖
sudo apt update
sudo apt install python3-pigpio

# 下载并编译pigpio
cd ~
wget https://github.com/joan2937/pigpio/archive/refs/heads/master.zip
unzip master.zip
cd pigpio-master
make -j
sudo make install

# 启动守护进程
sudo pigpiod
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
#!/usr/bin/env python3
import pigpio
import time
import signal
import sys

# 硬件PWM引脚(仅GPIO12, GPIO13, GPIO18, GPIO19支持硬件PWM)
PWM_PIN = 18
FREQUENCY = 1000000 # 1MHz
DUTY_CYCLE = 500000 # 50% (范围: 0-1000000)

pi = pigpio.pi()

def cleanup(signum=None, frame=None):
"""清理资源"""
pi.set_mode(PWM_PIN, pigpio.INPUT)
pi.stop()
print("\n资源清理完成")
sys.exit(0)

# 注册信号处理
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)

try:
# 设置硬件PWM
pi.set_mode(PWM_PIN, pigpio.OUTPUT)
pi.hardware_PWM(PWM_PIN, FREQUENCY, DUTY_CYCLE)

print(f"硬件PWM已启动: GPIO{PWM_PIN}, {FREQUENCY}Hz, 50%占空比")
print("按 Ctrl+C 停止")

while True:
time.sleep(1)

except Exception as e:
print(f"错误: {e}")
cleanup()

**方法2:gpiozero

1
2
# 安装gpiozero(通常已预装)
sudo apt install python3-gpiozero
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
#!/usr/bin/env python3
from gpiozero import PWMOutputDevice
import time
import signal
import sys

# 使用GPIO18(硬件PWM引脚)
pwm = PWMOutputDevice(18, frequency=1000)

def cleanup(signum, frame):
pwm.close()
print("\n资源清理完成")
sys.exit(0)

signal.signal(signal.SIGINT, cleanup)

try:
pwm.value = 0.5 # 50%占空比
print("硬件PWM已启动: 1kHz, 50%占空比")
print("按 Ctrl+C 停止")

while True:
time.sleep(1)

except Exception as e:
print(f"错误: {e}")
cleanup()
1
pwm_hw_test.py

第三步:使用示波器测量

  1. 打开示波器。
  2. 将探头连接到 物理引脚12 (GPIO 18)GND
  3. 在示波器上执行 自动设置 或手动调整:
    • 时基:调到 500μs/div 左右,以清晰看到1kHz的周期。
    • 电压刻度:调到 1V/div2V/div
    • 触发:设置为边沿触发,源选择你连接的通道,电平调到约1.6V (3.3V的一半)。
  4. 你应该能立即在屏幕上看到一个稳定的 方波
  5. 进行测量:
    • 频率:使用示波器的测量功能,选择“频率”,读数应接近 1.000 kHz
    • 占空比:选择“占空比”测量,读数应接近 50.0%
    • 电压:高电平应在 3.3V 左右,低电平在 0V

软件PWM

rpi-001-expriment

硬件PWM 10MHz

rpi-001-10M-expriment

树莓派PWM频率对比

软件PWM (RPi.GPIO)

  • 最高频率:约 1-10kHz (实际使用建议 ≤1kHz)
  • 限制因素:Python解释器性能、系统负载
  • 特点:所有GPIO都可用,但精度差、抖动明显

硬件PWM (pigpio/gpiozero)

  • 最高频率:理论上可达 125MHz (树莓派4)
  • 实际可用频率:通常 1kHz - 30MHz
  • 支持引脚:仅 GPIO12, GPIO13, GPIO18, GPIO19

具体频率对比表

方法 最高频率 推荐频率 精度 稳定性
RPi.GPIO软件PWM ~10kHz ≤1kHz
pigpio硬件PWM 125MHz 1kHz-30MHz 极高 优秀
gpiozero硬件PWM 125MHz 1kHz-30MHz 极高 优秀
Linux PWM子系统 125MHz 1kHz-30MHz 极高 优秀

第四步:实验扩展

修改代码中的 pwm.start(50),将 50 改为 10 (10%占空比) 或 90 (90%占空比),重新运行程序。再次用示波器测量,你会看到波形的“高电平”部分明显变短或变长,但频率保持不变。

恭喜你! 你已经成功地用软件生成一个精确的硬件信号,并用示波器完成了验证。这是嵌入式系统调试中最基础的技能之一。

compile the kernel

clone和compile在容器中操作

1
2
3
4
docker run --privileged -d -it --name angel -v `pwd`:/root/code -w /root/code ghcr.io/falcon-infra/dev:ubuntu24.04
docker exec -it angel /bin/zsh
git config --global user.name $your_name
git config --global user.email $your_email

clone code

1
2
3
git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git
cd linux-stable
gco v5.15.194

compile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 生成默认配置
make defconfig

# 2. 启用调试选项
./scripts/config --enable DEBUG_KERNEL
./scripts/config --enable DEBUG_INFO
./scripts/config --enable KGDB
./scripts/config --enable FTRACE
./scripts/config --set-val DEBUG_INFO_DWARF4 y

# 3. 自动处理依赖关系
make olddefconfig

# 4. 可选:检查配置
grep -E "DEBUG_KERNEL|DEBUG_INFO|KGDB|FTRACE" .config

# 5. 编译内核
make CC=clang -j$(nproc)

使用 BusyBox 创建最小 rootfs 的步骤如下:

  1. 下载并编译 BusyBox
    1
    2
    3
    4
    5
    6
    7
    wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
    tar xf busybox-1.36.1.tar.bz2
    cd busybox-1.36.1
    make defconfig
    make menuconfig # 可选:静态编译 Settings → Build static binary (no shared libs)
    make -j$(nproc)
    make install
1
2
3
4
5
6
7
8
9
# 1. 先拿一份默认配置
make defconfig

# 2. 用 sed 三行命令完成上述三项修改
sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
sed -i 's|CONFIG_PREFIX=.*|CONFIG_PREFIX="./rootfs"|' .config

# 3. 让 Kbuild 自动补齐依赖(重要!)
make oldconfig >/dev/null </dev/null

这里回到mac宿主机。

  1. 创建必要的目录结构
1
2
cd rootfs
mkdir -p {etc,dev,proc,sys,tmp,var,usr/bin,usr/sbin,lib,root}
  1. 创建基础设备节点
1
2
3
4
5
6
7
8
cd rootfs/dev

# 创建设备文件
sudo mknod console c 5 1
sudo mknod null c 1 3
sudo mknod zero c 1 5
sudo mknod ttyAMA0 c 204 64
sudo chmod 666 console null zero ttyAMA0
  1. 创建初始化脚本
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
cd rootfs
cat > init << 'EOF'
#!/bin/sh

echo "Starting system..."

# 挂载必要文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp

# 设置终端
exec </dev/ttyAMA0 >/dev/ttyAMA0 2>&1

# 设置环境变量
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export HOME=/root
export USER=root

# 显示系统信息
echo "=== BusyBox Linux ==="
echo "Kernel: $(uname -r)"
echo "System is ready!"

# 启动shell(修复job control)
exec setsid sh -c 'exec sh </dev/ttyAMA0 >/dev/ttyAMA0 2>&1'
EOF
chmod +x init
  1. 创建简单的fstab
1
2
3
4
5
cat > etc/fstab << 'EOF'
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
EOF
  1. 创建rootfs镜像
1
2
3
4
5
6
7
8
9
10
11
12
cd rootfs

# 创建CPIO格式的rootfs(用于initrd)
find . | cpio -H newc -o | gzip > ../rootfs.cpio.gz

# 或者创建ext4文件系统镜像
dd if=/dev/zero of=../rootfs.ext4 bs=1M count=64
mkfs.ext4 ../rootfs.ext4
mkdir -p /mnt/rootfs
sudo mount ../rootfs.ext4 /mnt/rootfs
sudo cp -ra . /mnt/rootfs/
sudo umount /mnt/rootfs
  1. 使用QEMU启动
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    qemu-system-aarch64 \
    -machine virt \
    -cpu cortex-a72 \
    -smp 4 \
    -m 2G \
    -kernel linux-stable/arch/arm64/boot/Image \
    -initrd busybox-1.36.1/rootfs.cpio.gz \
    -append "console=ttyAMA0 rdinit=/init" \
    -nographic \
    -no-reboot

✅ 今天学会:

认识时间变量 t

掌握位置函数的魔法公式

画出运动向量的轨迹 🎮 终极任务:设计你的精灵运动动画!

复习向量 - 从静态到动态

"上节课的'走路箭头'还记得吗?"

  • 第二课回顾:静态向量 {3,2} = 从原点走到(3,2)
  • 新发现:如果这个箭头会动呢?

课堂提问:箭头变长时,终点位置如何变化?(引出位置向量动态化)

认识新变量 - 时间魔法师

"小精灵的隐形遥控器 - 时间 t"

实验:学生用秒表计时,教师移动精灵玩偶

t(秒) 0 1 2
位置 起点 ? ?

t 是变量 - 像第一课的电阻/电压,可以自由改变!

"把时间翻译成位置的魔法"

教室活动:

学生A匀速向右走:x(t) = 2×t(步数)

学生B模拟下落:y(t) = 5 - 5×t²

板书公式:

1
2
3
位置函数: 
x(t) = 起点x + 速度x × t
y(t) = 起点y + 速度y × t - 5×t²

函数机器图解
graph LR
    t["输入 t"] --> 机器["魔法公式"] --> 输出["输出 (x(t),y(t))"]

联系第一课: 欧姆定律:输入V,R → 输出I 位置函数:输入t → 输出位置向量!

动手计算 - 我的精灵设计

"设计你的精灵飞行"

时间 t 计算 x(t)=1+2×t i计算 y(t)=3+4×t-5×t² 位置向量
0 1 3 (1, 3)
1 3 3+4-5=2 (3, 2)
2 5 3+8-20=-9 (5, -9)

任务:

  1. 选精灵(飞机/小鸟/超人)
  2. 自定起点和速度
  3. 完成表格计算

"把位置向量连起来!"

  • 学生活动:在坐标纸画出三个位置向量终点
  • 重大发现:三点连成抛物线!
1
ListPlot[{{1,3},{3,2},{5,-9}}, PlotRange->{{0,6},{-10,5}}]

教师强调: 每个位置向量是瞬间快照 连起来就是运动轨迹(函数的图像)

教学总结

通过时间变量、x(t)、y(t),两个位置函数组成向量。通过不同时间的t,表格绘制,感觉变化的向量。 要数形结合,结合前面变量、函数、向量的概念,让学生体会运动物体的轨迹,当时举的例子是抛一个🎾,水平的力让球有水平的动能,产生位移随时间变化,垂直方向是重力,高度随时间变化,产生轨迹。接下来学点概率呗。

四年级数学·暑期第二课

《小小向量大冒险——用 Mathematica 玩“位置魔法”》 (完整课件,1 课时 45 min)

————————— 【课程包】 PPT 10 页 + 学生讲义 1 张 + 教师 Mathematica 笔记本 1 份
(全部放在同名文件夹,打开即用)

————————— 课前 2 分钟 教师把教室地面贴成 6×6 方格,原点 (0,0) 贴星星贴纸,孩子踩格子热身。

————————— ## 课件流程

Slide 1 题目 & 目标
• 今天学会:用“小箭头”表示走路,用电脑把箭头画出来。
• 回家任务:画一张“我的上学路线图”。

Slide 2 故事
“小机器人 Turtle 收到两张藏宝图,先向右 3、向上 2,再向右 1、向上 3,它最后停在哪?”
(配图:两个箭头首尾相连)

Slide 3 动手踩格子
叫 2 位学生上台:
1 号同学从原点出发,按第一张图走;
2 号同学从 1 号终点出发,按第二张图走。
全班一起喊出最终格子坐标 (4,5)。
→ 把体验翻译成数学:
v = {3, 2},w = {1, 3},v + w = {4, 5}

Slide 4 Mathematica 第一次亮相
教师现场运行(大屏投影):

1
2
3
4
5
6
7
8
9
v = {3, 2}; w = {1, 3};
A = {0, 0};
Graphics[{
Red, Arrow[{A, A + v}],
Blue, Arrow[{A + v, A + v + w}],
Green, Arrow[{A, A + v + w}],
Black, PointSize[0.05], Point[A + v + w]
}, Axes -> True, GridLines -> Automatic,
PlotRange -> {{-1, 6}, {-1, 6}}]

孩子看到红箭头 + 蓝箭头 = 绿箭头,形状像滑梯。

Slide 5 向量加法公式(老师板书)
v = (x₁, y₁)
w = (x₂, y₂)
v + w = (x₁ + x₂, y₁ + y₂)
口诀:横加横,竖加竖。

Slide 6 “魔法缩放”滑条
教师拖动滑条 s,箭头忽大忽小:

1
2
3
4
5
6
Manipulate[
Graphics[{
Red, Arrow[{{0, 0}, s*{3, 2}}]
}, Axes -> True, PlotRange -> {{-5, 5}, {-5, 5}}],
{s, -2, 2, 0.1}
]

学生喊:
“s = 2 变两倍!”
“s = -1 反方向!”

Slide 7 长度(拓展给学有余力的孩子)
|v| = √(x² + y²) (老师口算 3²+2²=13,电脑 √13≈3.6)
告诉孩子:箭头越长,走路越远。

Slide 8 小挑战
教师发一张练习纸:
1. 画向量 u = {2,4}。
2. 计算并画出 2u。
3. 写出 2u 的长度。
(5 min 完成,同桌交换批改)

Slide 9 回家任务
• 量一量:从“家”到“学校”横走几格,竖走几格,写成向量 h。
• 拍照发群里,老师用 Mathematica 把全班的 h 画成一张“上学地图”。

Slide 10 预告下一课
“把 4 个箭头围成四边形——发现平行四边形面积的秘密!”

—————————
学生讲义(A5 双面)

正面:
【向量小卡片】
v = (→ , ↑)  w = (→ , ↑)  v + w = ( , )

【魔法缩放】
把 v 放大 2 倍:2v = ( , )

背面:
【回家任务单】
1. 我的上学向量 h = (___ , ___)。
2. 画在方格纸上,拍照。
3. 明天带来,拼成“全班上学地图”!

—————————
教师 Mathematica 笔记本(已写好,直接运行)
文件名:Lesson2_Vector.nb
包含:
• 踩格子演示 cell
• 加法动画 cell
• 缩放滑条 cell
• 自动收集学生照片并绘图的函数:

1
2
3
DrawClassMap[list_] := 
Graphics[{Arrow[{{0, 0}, #}] & /@ list}, Axes -> True,
PlotRange -> {{-10, 10}, {-10, 10}}]

—————————
一句话总结(给孩子)
“把两个走路指令加起来,就是一次走到底!”

课程记录

开场讲了下x,y坐标,然后家到学校的走法。接下来用讯飞本子上画了两个向量,以及他们相加,进入第一个mathematica的作图,结合代码进行讲解调试。

第二步讲了向量的加法,具体计算步骤,然后给出公式。

第三步讲了标量乘向量,用methematica的可操作图进行讲解分析。

进一步讲解了norm的计算,估计v{6, 4}的长度。介绍了底层的计算原理。

最后让果果画出u={3, 2}和2u。

x86的一堆软件重新装一下,主要是emacs重新编译native。

emacs 30.1重新编译

prepaer

1
2
3
4
5
brew install autoconf automake cmake libgccjit gnutls texinfo jansson tree-sitter gcc libgccgit
curl -O https://mirrors.ustc.edu.cn/gnu/emacs/emacs-30.1.tar.xz
tar -xvf emacs-30.1.tar.xz
cd emacs-30.1
./autogen.sh

. gcc_env.sh

1
2
3
export CC="/opt/homebrew/opt/gcc/bin/gcc-15"
export CXX="/opt/homebrew/opt/gcc/bin/g++-15"
export LIBRARY_PATH="/opt/homebrew/opt/gcc/lib/gcc/15:$LIBRARY_PATH"

configure and make

1
2
3
4
5
6
7
8
./configure \
--with-native-compilation \
--with-json \
--with-tree-sitter \
--with-gnutls \
--without-ns
make -j
make install

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ doom doctor
The doctor will see you now...

> Checking your Emacs version...
> Checking for Doom's prerequisites...
> Checking for Emacs config conflicts...
> Checking for missing Emacs features...
> Checking for private config conflicts...
> Checking for common environmental issues...
> Checking for stale elc files...
> Checking for problematic git global settings...
> Checking Doom Emacs...
✓ Initialized Doom Emacs 3.0.0-pre
✓ Detected 37 modules
✓ Detected 133 packages
> Checking Doom core for irregularities...
Found Symbols Nerd Font Mono
> Checking for stale elc files in your DOOMDIR...
> Checking your enabled modules...

Everything seems fine, happy Emacs'ing!

好嘞!整得挺明白啊老铁!Emacs 整得溜光水滑的,这回可劲儿造吧!代码敲得咔咔的,插件跑得嗖嗖的,那叫一个得劲儿!有啥不明白的随时吱声,咱这嘎达给你支棱起来!开心就完事儿了! 😄

第一课:变量与函数——从欧姆定律开始

(适合三年级以上,结合电路实验)

📌 课程目标

  1. 理解“变量”:在电路中,电压(V)、电流(I)、电阻(R)都是可以变化的量,它们就是变量
  2. 理解“函数”:欧姆定律(V = I × R)就是一个函数,它告诉我们变量之间的关系
  3. 动手实验:用真实的电路测量、观察变量如何变化,并用万用表记录数据,绘制函数曲线。

🔧 实验材料

  • 电路元件:电池(9V 或 2节AA)、电阻(1kΩ、10kΩ)、电位器(可调电阻)、LED、面包板(可选)
  • 测量工具:万用表(测电压、电流、电阻)
  • 其他:导线、纸笔(记录数据)、坐标纸(画函数图)

🚀 实验步骤

1. 认识变量:电压、电流、电阻

🔹 电压(V):电池的“推力”,单位是伏特(V)。
🔹 电流(I):电子的“流动”,单位是安培(A),实验中常用毫安(mA)。
🔹 电阻(R):阻碍电流的“关卡”,单位是欧姆(Ω)。

小实验:用万用表测量

  • 测电池电压(V):万用表拨到 DCV 20V,红表笔接电池+,黑表笔接电池-。
  • 测电阻(R):万用表拨到 Ω(欧姆档),直接测电阻的阻值。
  • 测电流(I):万用表拨到 DCA 20mA串联进电路(后面实验会做)。

2. 欧姆定律实验:V = I × R

电路搭建:

1
电池 (+) —— 电位器(可调 R) —— 固定电阻(保护 LED) —— LED —— 电池 (-)
实验步骤:
1. 调节电位器(改变 R),用万用表测量当前的电阻值(R)。
2. 串联万用表(测电流 I)
- 把万用表拨到 DCA 20mA断开 LED 和电阻的连接,把表笔串联进去(红表笔接电阻,黑表笔接 LED)。
3. 记录数据
- 调节电位器 5 次,每次记录 R(Ω)I(mA)

实验次数 电阻 R (Ω) 电流 I (mA) 计算 V = I × R
1 1000 9.0 9.0 × 1000 = 9V
2 2000 4.5 4.5 × 2000 = 9V
3 3000 3.0 3.0 × 3000 = 9V
4 4000 2.25 2.25 × 4000 = 9V
5 5000 1.8 1.8 × 5000 = 9V

观察规律:
电阻 R 变大,电流 I 变小(反比关系)。
V = I × R 始终≈电池电压(9V),验证欧姆定律!


3. 绘制函数曲线:I = V / R

在坐标纸上:
- X 轴:电阻 R(Ω)
- Y 轴:电流 I(mA)
- 画点:把实验数据标在图上,连成曲线。

结论:
- 这条曲线就是 函数 I = V / R 的图形!
- 电阻 R 是输入变量,电流 I 是输出变量,欧姆定律就是它们之间的关系!


4. 计算机思维连接:变量 & 函数

🔹 变量就像“盒子”
- 在计算机里,RIV 都是变量,可以存储不同的值。
- 比如:

1
2
3
4
R = 1000  # 电阻是 1000Ω
V = 9 # 电池是 9V
I = V / R # 计算电流 I
print(I) # 输出 0.009 A(即 9mA)
🔹 函数就像“魔法机器”
- 欧姆定律就是一个函数:
1
2
def calculate_current(V, R):
return V / R
- 输入 VR,输出 I


🎯 课后挑战

  1. 改变电池电压(V):用 2 节 AA 电池(3V)再做实验,看看 IR 的关系是否还是 I = V / R
  2. 用 Scratch 模拟:在 Scratch 里做一个“欧姆定律计算器”,输入 VR,自动计算 I

📝 总结

变量:电路中可以变化的量(V、I、R)。
函数:变量之间的关系(欧姆定律 V = I × R)。
实验:用真实电路测量数据,画出函数曲线!
计算机连接:变量存储数据,函数计算关系!

下一课:电容充放电——时间变量和指数函数! 🚀

课程回顾

mathematica代码

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
data = {{3.0, 3.0, 1}, {3.0, 1.5, 2}, {3.0, 1.3, 2.2}, {3.7, 1.7, 
2.2}, {3.0, 0.8, 3.3}, {3.8, 1.1, 3.3}, {4.5, 1.3, 3.3}, {3.5,
0.7, 4.7}, {4.8, 1, 4.7}, {4.8, 0.9, 5.1}, {4.3, 0.8, 5.1}};

(*提取电压=3.0V的数据并按电阻排序*)
constantVoltageData = Select[data, #[[1]] == 3.0 &];
sortedData = SortBy[constantVoltageData, #[[3]] &];

ListLinePlot[sortedData[[All, {3, 2}]], PlotMarkers -> Automatic,
Frame -> True, FrameLabel -> {"电阻 (k\[CapitalOmega])", "电流 (mA)"},
PlotLabel -> "电压恒定(3.0V): 电阻\[UpArrow], 电流\[DownArrow]",
FrameStyle -> Directive[Black, 12], GridLines -> Automatic,
ImageSize -> 400]
(*按电阻值分组*)groupedData = GatherBy[data, #[[3]] &];
(*只保留有多个数据点的组*)
multiPointGroups = Select[groupedData, Length[#] > 1 &];

ListLinePlot[#[[All, {1, 2}]] & /@ multiPointGroups,
PlotMarkers -> Automatic, Frame -> True,
FrameLabel -> {"电压 (V)", "电流 (mA)"},
PlotLabel -> "电阻恒定: 电压\[UpArrow], 电流\[UpArrow]",
FrameStyle -> Directive[Black, 12],
PlotLegends -> ("R = " <> ToString[#[[1, 3]]] <>
" k\[CapitalOmega]" & /@ multiPointGroups),
GridLines -> Automatic, ImageSize -> 400]
constantVoltageData = {{3.0, 3.0, 1}, {3.0, 1.5, 2}, {3.0, 1.3,
2.2}, {3.0, 0.8, 3.3}};
TableForm[constantVoltageData,
TableHeadings -> {None, {"电压(V)", "电流(mA)", "电阻(k\[CapitalOmega])"}}]
(*绘制实验数据点*)
dataPoints =
ListPlot[constantVoltageData[[All, {3, 2}]],
PlotStyle -> {Red, PointSize[0.02]}, Frame -> True,
FrameLabel -> {"电阻R (k\[CapitalOmega])", "电流I (mA)"},
GridLines -> Automatic, PlotRange -> All];

(*绘制理论函数 I=3.0/R*)
theoreticalPlot =
Plot[3.0/r, {r, 0.5, 5}, PlotStyle -> Blue,
PlotLegends -> {"I = 3.0/R"}];

(*合并图形*)
Show[theoreticalPlot, dataPoints,
PlotLabel -> "函数关系: 电流I = 电压V / 电阻R"]
V = 3.0;
predictedI = V/5 (*输出:0.6 mA*)
Manipulate[
Plot[V/r, {r, 1, 10}, Frame -> True,
FrameLabel -> {"R (k\[CapitalOmega])", "I (mA)"}], {V, 1, 10, 0.1},
Initialization :> (V = 3.0)]
TableForm[Table[{r, 3.0/r}, {r, 1, 5, 0.5}],
TableHeadings -> {None, {"R (k\[CapitalOmega])", "I (mA)"}}]
  1. 实验测试了多组数据记录
  2. 用mathematica进行可视化讲解
  3. 用计算器进行一些验证
  4. 讨论分析为什么理论和测量的偏差
  5. 最后给出V=3的I和R的函数,计算和实验

doom sync会遇到网络问题。解决方法如下:

1
2
git clone --depth 1 https://github.com/hlissner/doom-emacs ~/.config/emacs
sed -i '129s|:repo "https://git.savannah.gnu.org/git/emacs/nongnu.git"|:repo "https://github.com/emacsmirror/nongnu_elpa.git"|' ~/.config/emacs/lisp/lib/packages.el

然后doom install或者doom sync

提供一个开发环境镜像dev-machine

东明山

位置:浙江省杭州市余杭区,毗邻良渚文化遗址区,属天目山余脉。 特色: - 自然生态:森林覆盖率超95%,以竹林、古树为胜,空气清新,适合徒步、骑行。 - 人文古迹:内有东明寺(传建文帝隐居地)、明代古道等历史遗迹。 - 休闲体验:登山步道、露营基地,春季可赏樱,秋季观红叶。

东明山踏春

皋亭山

皋亭山位于中国浙江省杭州市东北部,属天目山余脉,海拔约361米。其历史文化悠久,是杭州重要的生态绿肺和休闲胜地,以桃文化闻名,春季桃花盛开时吸引众多游客。周边有千桃园、皋亭山景区等景点,融合自然风光与人文遗迹(如南宋班师亭、抗元英雄文天祥纪念地)。交通便利,适合徒步、骑行等户外活动。

一游皋亭山王子安 二游皋亭山王子安 皋亭山玩泥巴泥

这个电路主要功能是三个LED灯轮流闪烁,通过RC充放电,以及三极管的导通截止来控制。

例8

一闪一闪亮晶晶。 实现电路

西溪元宵灯会

西溪元宵灯会,是杭州西溪湿地一年一度的传统盛会。每逢元宵佳节,西溪湿地便化身为灯光的海洋,各式各样的花灯点缀其间,既有传统的龙灯、鱼灯,也有现代创意灯组,流光溢彩,美不胜收。游客可以漫步在灯影交织的小径上,感受浓厚的节日氛围,体验传统文化的魅力。灯会期间,还有猜灯谜、赏民俗表演等活动,热闹非凡。2025年元宵节,不妨去西溪灯会,感受一场视觉与文化的盛宴!

元宵快乐

元宵节,给上2025的祝福

2025年元宵节,愿你月圆人圆事事圆,花好灯好心情好!愿新的一年,幸福如汤圆般甜甜蜜蜜,事业如花灯般红红火火!

Good Luck

这个电路主要功能是将麦克风输入的声音信号进行两级放大,并在信号强度达到一定程度时点亮LED指示灯。

例7

随便吼两嗓。 实现电路

长白山

长白山位于中国吉林省东南部,是中朝界山,主峰白头山海拔2749米。长白山以其丰富的自然资源和独特的火山地貌闻名,拥有天池、瀑布、温泉等景观。它是松花江、图们江和鸭绿江的发源地,也是东北虎、东北豹等珍稀动物的栖息地。长白山被列为世界自然遗产,是重要的生态旅游和科研基地。

新春快乐

春节长白山旅游,做一首诗

《春节游长白山》 雪覆长白岭,冰封天池边。 松涛迎远客,温泉暖寒天。 爆竹辞旧岁,灯笼映新年。 登高望故土,心随云海翩。

小雪人

2025年祝福

2025年,愿您如长白山的松柏,坚韧不拔;如天池的湖水,清澈宁静。愿您的事业如瀑布般奔腾不息,生活如温泉般温暖舒适。新年快乐,万事如意!

和糖糖一起
0%