(45分钟魔法课 · 适合6-12岁小朋友)

课程目标(今天彻底玩懂这个超级魔法)

  • 看懂递归:一个函数可以自己召唤自己,像俄罗斯套娃一层套一层!
  • 牢牢记住两大铁律:停止条件 + 自己召唤更小的问题
  • 亲眼看到递归怎么一步步“展开”又“收回来”
  • 用身体真实感受“调用”和“返回”的过程(动觉学习者狂喜!)
  • 学会3个最简单最可爱的递归函数
  • 自己动手做4个小练习

核心口诀(全班一起喊3遍!):
“Big problem = smaller same problem + call myself again!
First say STOP! or it will spin forever~”


第一部分:问题引入(5分钟)—— 果果收到无限糖果套娃盒!

故事时间:
果果的魔法糖果店今天收到一个大礼盒!打开一看,里面有一个一模一样的更小糖果盒,再打开还有更小……一直到最小的一颗小糖果!

果果问:“这个盒子一共有多少层啊?怎么才能知道呢?”

这就是递归的魔法——不用一个一个数,而是让“自己”去打开更小的盒子,直到打开最小的那一个!

(老师可以让小朋友拿纸画一排套娃,或者用手机搜“Russian nesting doll”看视频)


第二部分:递归两大铁律(讲透!10分钟)—— 像剥洋葱一样

铁律1:停止条件(Base Case)
“到了最小的问题,就直接回答,不再召唤自己!”
→ 就像套娃里最小的那个娃娃,打开就到底了,不用再打开。

铁律2:递归调用(Recursive Call)
“把大问题变成一个小一点的同样问题 + 自己再召唤一次!”
→ 大盒子的问题 = 打开它 + 让更小的盒子自己解决

可视化:展开 vs 收回来
想象果果在剥洋葱:
一层一层剥下去(展开)→ 剥到最中心(停止)→ 再一层一层包回去(收回来)


第三部分:可视化一步步展开(重头戏!12分钟)—— 3个超级简单例子

我们用超级慢动作一步一步看清楚!(函数全部用英文命名)

例子1:countdown-candies(倒数吃糖果)

1
2
3
4
5
6
7
8
(define (countdown-candies n)
(if (= n 0) ; 停止条件
(printf "Stop! Eat candy! 🍭\n")
(begin
(printf "Candy ~a !\n" n)
(countdown-candies (- n 1))))) ; 自己召唤自己

(countdown-candies 3)

一步步可视化展开(老师边说边在黑板画树):

1
2
3
4
5
6
7
8
9
10
11
countdown-candies(3)
Print "Candy 3 !"
↓ call countdown-candies(2)
Print "Candy 2 !"
↓ call countdown-candies(1)
Print "Candy 1 !"
↓ call countdown-candies(0)
→ Stop! Eat candy! 🍭 ← BASE CASE!
↑ return
↑ return
↑ return

例子2:sum-candies(糖果总颗数)

1
2
3
4
5
6
7
(define (sum-candies lst)
(if (empty? lst) ; 停止条件:盒子空了
0
(+ (first lst) ; 拿第一颗
(sum-candies (rest lst))))) ; 自己算剩下的

(sum-candies (list 5 3 2)) ; → 10

可视化树形展开:

1
2
3
4
sum-candies([5,3,2]) = 5 + sum-candies([3,2])
= 5 + (3 + sum-candies([2]))
= 5 + (3 + (2 + sum-candies([])))
= 5 + (3 + (2 + 0)) = 10

例子3:candy-factorial(糖果打包组合数)

1
2
3
4
5
6
(define (candy-factorial n)
(if (= n 0) ; 停止条件
1
(* n (candy-factorial (- n 1)))))

(candy-factorial 4) ; → 24

一步步展开:

1
2
3
4
candy-factorial(4) = 4 × candy-factorial(3)
= 4 × (3 × candy-factorial(2))
= 4 × (3 × (2 × candy-factorial(1)))
= 4 × (3 × (2 × (1 × 1))) = 24
果果编程第8课


超级动觉游戏时间!(3分钟)—— 站起来玩人体俄罗斯套娃!

(放在第三部分结束后,全班站起来,动觉学习者最爱!)

老师说:“现在我们用身体来感受递归!大家站起来排成一排(3-5人一组)!”

游戏玩法(老师带头示范): - 老师是大娃娃(第1层),大声喊:“I want to know total number of people!” - 老师拍拍同学A肩膀:“You are my smaller me! You calculate how many left!” - 同学A拍拍同学B:“You are my smaller me! You calculate how many left!” - …… 一直拍到最后一个同学(最小娃娃)大喊:“Stop! Only me 1 person!”(停止条件) - 然后层层返回: - 最后一个同学告诉前面的人:“I have 1!” - 前一个人加上自己再告诉上一个人:“I have 2!” - …… 最后老师宣布:“Total 3 people!”

全班玩2-3组,边玩边喊“call down!” “return up!”
孩子们笑成一团,但瞬间就懂了“调用下去”和“返回上来”的真实感觉!


第四部分:简单练习(10分钟 + 回家作业)

课堂马上试试(老师带做): 1. 改 countdown-candies 从5数到1,打印“Eat the Xth candy!” 2. 写 candy-sum n 求 1+2+…+n (提示:(+ n (candy-sum (- n 1)))) 3. 写 candy-length lst 返回列表里有几颗糖果(空列表返回0) 4. 想一想生活中哪里还有“自己召唤自己”:折纸、讲故事“从前有座山……”、剥橘子…

回家作业(截图发爸爸妈妈或果果老师): 1. 运行 candy-factorial 5,手写展开过程(像上面那样画箭头) 2. 用 candy-sum 6 算出结果,并告诉家人这是什么意思 3. 把人体游戏拍个小视频,发给果果老师看~


下节课预告
第009课:魔法升级!把递归 + map + filter 合成超级糖果店管理系统

老师寄语
哇!今天果果不但用眼睛看懂了,还用身体玩懂了自己召唤自己的无限魔法
递归就像果果长大:每解决一个小问题,就离大梦想更近一步~

把今天的英文函数代码粘到DrRacket跑一跑,手写展开过程,玩人体游戏拍视频,发给我们看!
下课啦!记得多吃糖果(适量哦)🍭✨

(本课100%聚焦基础原理、全程可视化+身体游戏、函数全英文命名、完美45分钟节奏,直接复制上课即可~)

(45分钟魔法课 · 适合6-12岁小朋友)

课程目标(今天学会这些大魔法)

  • 认识“for家族”:Racket里最强大、最好玩的遍历魔法!
  • for/list + #:when 完美解决第6课的遗留问题:只挑出「翻倍后加税 >100元」的超级贵糖果!
  • 学会 forfor/listfor/sumfor/firstfor/or 等小精灵。
  • 还能用 in-rangein-naturals 做计数、打印、求和。
  • 把果果糖果店计算器升级成超级智能版

核心口诀
filter 只能筛一次,for 家族能又筛又数又挑又算,一口气搞定所有事!”

第一部分:复习第6课(5分钟)

上节课我们用五大超级魔法做了什么?

1
2
3
4
5
6
7
8
9
(define add-tax (lambda (p) (+ p (* p 0.1))))
(define double (lambda (p) (* p 2)))
(define to-yuan (lambda (p) (string-append (number->string p) "元")))

(define prices (list 10 50 100 30 55 120))

;; 第6课做法(用filter + compose)
(filter (lambda (p) (> (add-tax (double p)) 100))
prices)

遗留问题:我们只拿到了数字,还想要“110元”“220元”这种带文字的漂亮列表,而且想直接在遍历时就过滤!

今天我们用for家族一口气解决!

第二部分:认识“流”(stream)—— 懒惰的魔法数字链(8分钟)

先来两个新朋友:

1
2
(in-range 10)          ; 魔法流:0 1 2 3 4 5 6 7 8 9
(in-naturals) ; 无限魔法流:0 1 2 3 ... 永远不会结束!

比喻:普通list是“一次性把所有糖果倒出来”,stream是“客人要一个才给一个”,超级省力气!

小测试:

1
2
(stream-first (in-range 5))   ; → 0
(stream-ref (in-range 10) 7) ; → 7

第三部分:for家族登场!(20分钟)

1. 最简单 for(像display一样打印)

1
2
3
4
5
6
7
(for ([p prices])
(display (to-yuan p))
(newline))
;; 输出:
;; 10元
;; 50元
;; ...

2. for/list —— 收集成新列表(最常用!)

1
2
3
(for/list ([p prices])
(to-yuan (add-tax (double p))))
;; → '("22元" "110元" "220元" "66元" "121元" "264元")

3. 加过滤魔法 #:when —— 解决遗留问题!

1
2
3
4
5
6
7
(define (super-price lst)
(for/list ([p lst]
#:when (> (add-tax (double p)) 100)) ; 只留下 >100元
(to-yuan (add-tax (double p)))))

(super-price prices)
;; → '("110元" "220元" "121元" "264元")

哇! 第6课的遗留问题一秒解决!🎉

4. for/sum —— 自动求和(买糖果总价神器)

1
2
3
(for/sum ([p prices])
(add-tax (double p)))
;; → 803.0 (所有糖果翻倍加税后的总价)

5. for/first & for/last —— 挑第一个/最后一个符合条件的

1
2
3
4
5
6
7
(for/first ([p prices] #:when (> (add-tax (double p)) 100))
(to-yuan (add-tax (double p))))
;; → "110元" (第一个超级贵糖果)

(for/last ([p prices] #:when (> p 50))
p)
;; → 120 (最后一个>50的原始价格)

6. for/or —— 只要有一个满足就返回 #t

1
(for/or ([p prices]) (> p 100))   ; 有没有超过100元的? → #t

7. 带计数的魔法(in-naturals)

1
2
(for ([p prices] [i (in-naturals)])
(printf "第~a个糖果:~a\n" i (to-yuan p)))

第四部分:实战升级糖果店计算器(10分钟)

把第6课的超级计算器全部换成 for 写法,更简洁!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(define (magic-shop prices)
(printf "=== 果果魔法糖果店 ===\n")
(for ([p prices] [i (in-naturals)])
(printf "第~a个:原价~a → 会员价~a → 实付~a\n"
i
(to-yuan p)
(to-yuan (double p))
(to-yuan (add-tax (double p)))))

(printf "\n超级贵糖果(>100元):~a\n"
(super-price prices))

(printf "今日总收入:~a\n"
(to-yuan (for/sum ([p prices]) (add-tax (double p))))))

(magic-shop (list 10 50 100 30 55 120))

第五部分:小练习(回家作业)

  1. for/list + #:when 做出「只显示打5折后仍然 >60元的糖果」
  2. for/sum 计算1到100所有偶数的平方和(提示:in-range 1 101 2
  3. 挑战:用 for*(嵌套版)打印九九乘法表(选做)
果果编程第7课

下节课预告

第8课:递归魔法 —— 像俄罗斯套娃一样,自己调用自己,无限强大!
(会用递归做出“汉诺塔搬糖果”“无限糖果树”)


老师寄语
今天学会了 for 家族,果果糖果店从“手动挑糖果”变成了“魔法自动流水线”!
把今天的代码复制到 DrRacket 里跑一跑,截图发给爸爸妈妈看~

下课!下节见!🍭✨

课程目标(45分钟)
学会使用 condfilterfoldrapplycompose 这五个超级魔法,让程序变得更聪明、更简洁、更强大!

核心口诀
小魔法一个一个用很累,把它们串起来,就能变出超级大魔法!

今天的故事背景
果果开了一家“魔法糖果店”!
客人买糖果有不同优惠规则,还要算总价、挑贵的糖果、给价格加税……
我们要用新学的魔法,一口气把这些事情都变得超级简单!

第一部分:问题引入(5分钟)

老师开场:

“上节课我们学会了 ifmap,果果已经能给每个价格加标签了!
但是现在客人买很多糖果,要做这些事:

  1. 根据数量决定打几折(10个以上9折、5-9个95折……)
  2. 从一堆糖果里挑出贵的(>50元的)
  3. 算所有糖果的总价
  4. 把价格先翻倍、再加10%税、最后变成“XX元”显示

如果还用嵌套 if 和好几个 map,代码会变得很长很乱!
今天我们学习5个超级魔法,把这些小步骤像搭积木一样连起来!”

板书示意图:

1
2
3
4
5
6
7
8
9
10
11
原材料(糖果数量、价格列表)

[cond] → 决定折扣

[filter] → 挑出贵的

[foldr] → 算总价

[compose] → 超级价格流水线(翻倍 → 加税 → 转文字)

超级结果!

第二部分:多条件选择神器 —— cond(8分钟)

基本写法

1
2
3
4
5
6
(define count 7)

(cond [(>= count 10) "9折!超级优惠🌟"]
[(>= count 5) "95折!很棒哦👍"]
[(> count 0) "原价购买~💰"]
[else "下次再来玩哦~😊"])

运行结果(count = 7):"95折!很棒哦👍"

比喻
cond 就像一个有很多岔路的选择精灵,每条路都有一个问题,只要回答“是”,就走这条路,不用一层套一层 if 了!

动手练习(学生自己改数字试试)

1
2
3
4
5
6
(define weather "cloudy")

(cond [(string=? weather "sunny") "去踢足球!⚽"]
[(string=? weather "cloudy") "去画画!🎨"]
[(string=? weather "rainy") "在家看动画!📺"]
[else "先睡觉吧~😴"])

第三部分:魔法筛子 & 魔法累加器(12分钟)

1. filter —— 魔法筛子

1
2
3
4
(define prices (list 10 60 35 120 45 80))

(filter (lambda (p) (> p 50)) prices)
; 结果:'(60 120 80)

比喻:filter 就像一个会挑东西的筛子,只留下符合条件的糖果。

练习:挑出80分以上的成绩

1
2
3
(define scores (list 95 82 68 91 75 88 60))
(filter (lambda (s) (>= s 80)) scores)
; 结果:'(95 82 91 88)

2. foldr —— 魔法累加器(从右往左累加)

1
2
3
4
5
(define prices (list 10 60 35 120))

(foldr + 0 prices) ; 225
(foldr * 1 (list 2 3 4)) ; 24
(foldr string-append "" (list "果" "果" "最" "棒")) ; "果果最棒"

比喻:foldr 就像一个从右边开始“叠加”的小工人,把所有东西按规则加/乘/拼接起来。

练习:算及格成绩的总分

1
2
3
(define scores (list 95 82 68 91 75 88 60))
(define pass-scores (filter (lambda (s) (>= s 60)) scores))
(foldr + 0 pass-scores) ; 总分

第四部分:超级组合技 —— apply & compose(15分钟)

1. apply —— 一口气喂饱魔法

1
2
3
(apply + (list 10 20 30 40))          ; 100
(apply max (list 95 82 68 91 75)) ; 95
(apply string-append (list "果果" " " "超" "棒")) ; "果果 超棒"

比喻:apply 就像把一篮子东西直接倒进魔法锅里,不用一个一个喂。

2. compose —— 终极魔法流水线(最重要!)

完整可运行代码(注意要加 require)

1
2
3
4
5
6
7
8
9
10
11
#lang racket

(define add-tax (lambda (p) (+ p (* p 0.1)))) ; 加10%税
(define double (lambda (p) (* p 2))) ; 翻倍
(define to-yuan (lambda (p) (string-append (number->string p) "元")))

(define super-price
(compose to-yuan add-tax double)) ; 从右往左:先翻倍 → 加税 → 转文字

(map super-price (list 10 50 100))
; 结果:'("22元" "110元" "220元")

计算过程(以 50 为例): 1. double → 100 2. add-tax → 100 + 10 = 110 3. to-yuan → "110元"

超级练习:自己设计一条流水线

1
2
3
4
5
6
7
8
9
; 目标:先×3 → 再+20 → 最后加“分”变成字符串
(define ×3 (lambda (n) (* n 3)))
(define add20 (lambda (n) (+ n 20)))
(define to-fen (lambda (n) (string-append (number->string n) "分")))

(define super-score (compose to-fen add20 ×3))

(map super-score (list 10 20 30))
; 应该得到:'("50分" "80分" "110分")

第五部分:综合创作时间(5分钟)

终极挑战:果果的超级糖果店计算器

请小朋友组合使用今天学的魔法,完成以下功能(老师可以给提示):

输入:糖果价格列表 + 购买数量
输出: - 先用 cond 判断能打几折 - 用 filter 挑出 >50 元的贵糖果 - 用 foldr 算总价(可先打折) - 用 compose 做最终价格显示(翻倍模拟“会员价” → 加税 → 加“元”)

鼓励孩子们自由发挥,写出最酷的组合方式!

第六部分:总结与作业(3分钟)

今天学会的五大超级魔法

  • cond → 多条件选择,比嵌套 if 更清晰
  • filter → 魔法筛子,挑出想要的
  • foldr → 累加/拼接神器
  • apply → 一口气喂一堆参数
  • compose → 把多个魔法串成流水线(最强!)

回家作业(三选二)

  1. cond 写一个“今天心情决定器”(心情 + 天气 → 决定做什么)
  2. filter + foldr 计算 (list 45 78 92 65 88 95 72) 中 85 分以上的总分和平均分
  3. compose 设计一个“超级分数处理器”:先×2 → 再+15 → 最后加“超级分”三个字
果果编程第6课

下节课预告
“下节课我们要学习递归魔法
一个魔法可以自己召唤自己,就像俄罗斯套娃一样,无限强大!
准备好迎接最酷的循环方式了吗?✨”

果果们,今天你们已经从“单个小魔法师”升级成了“魔法工厂工程师”!
继续加油,下节课见~ 🚀

遗留问题,过滤>100元的。

1
2
3
4
5
6
7
8
9
10
11
#lang racket
(define add-tax (lambda (p) (+ p (* p 0.1)))) ; 加10%税
(define double (lambda (p) (* p 2))) ; 翻倍
(define to-yuan (lambda (p) (string-append (number->string p) "元")))

(define (super-price lst)
(for/list ([p lst]
#:when (> (add-tax (double p)) 100))
(to-yuan (add-tax (double p)))))

(super-price (list 10 50 100))

课程目标(45分钟)
学会 if:让程序根据不同情况,做出不同选择!
核心口诀:如果(条件)就做A,否则做B

整体风格延续
- 口语化故事教学(果果攒钱、精灵冒险)
- 比喻:如果魔法 = “十字路口的选择精灵”
- 结合前几课:变量、列表、图形、lambda
- 逐步递进:从简单判断 → 嵌套判断 → 在map里用if
- 预告下节:compose 魔法(把多个机器连成超级机器)


第一部分:问题引入(5分钟)

老师故事
“果果攒了好多零花钱,上节课我们用 map 把所有钱都翻倍了!
现在果果要去买最新款小天才手表(2399元)!

如果钱够了 → 果果开心跳舞!🎉
如果钱不够 → 果果伤心继续攒钱 😢

电脑怎么知道‘够不够’?今天教你一个超级魔法:如果魔法
它就像路口的选择精灵:看情况,带你走不同的路!”

板书十字路口图:

1
2
3
4
5
6
7
8
┌──────────────┐
│ 钱够吗? │
└──────┬───────┘

是 │ 否
┌──▼───┐ ┌──▼───┐
│ 买! │ │ 攒! │
└──────┘ └──────┘


第二部分:认识 if 魔法(15分钟)

1. 基本语法(5分钟)

1
2
3
4
5
6
7
#lang racket
(define my-money 2500) ; 果果现在的钱
(define watch-price 2399) ; 手表价格

(if (> my-money watch-price)
"可以买!开心!🎉"
"还差一点,继续攒钱😢")

运行结果:"可以买!开心!🎉"

解释: - if = 选择精灵启动 - (> my-money watch-price) = 判断条件(真或假) - 第一个字符串 = 条件为真时做的事 - 第二个字符串 = 条件为假时做的事

比喻
if 就像一个有三个口袋的精灵:
1. 放判断问题
2. 放“对的时候做的事”
3. 放“错的时候做的事”

2. 动手练习(10分钟)

练习1:钱够不够买玩具
改数字试试不同结果!

1
2
3
4
5
6
(define my-money 2000)
(define toy-price 2200)

(if (>= my-money toy-price)
"买买买!"
"再攒攒~")

练习2:图形版如果魔法(结合第一课)

1
2
3
4
5
6
7
(require 2htdp/image)

(define mood "happy") ; 改成 "sad" 试试!

(if (string=? mood "happy")
(circle 50 "solid" "yellow") ; 开心画太阳
(circle 50 "solid" "blue")) ; 伤心画乌云

练习3:数字判断大挑战

1
2
3
4
5
6
7
(define score 85)

(if (>= score 90)
"优秀!得小红花🌸"
(if (>= score 70)
"不错!继续加油🚀"
"要努力哦~"))

老师提示:可以把 if 套 if! 这叫“嵌套判断”


第三部分:如果魔法 + 列表 + map(15分钟)

故事
“果果有好多朋友的成绩单!我们要给每个人发不同鼓励的话!”

1
2
3
4
5
6
7
8
9
(define scores (list 95 82 68 91 75))

(map (lambda (s)
(if (>= s 90)
"超级棒!🌟"
(if (>= s 80)
"很不错!👍"
"加油哦!💪")))
scores)

结果:'("超级棒!🌟" "很不错!👍" "加油哦!💪" "超级棒!🌟" "加油哦!💪")

小挑战:价格标签升级版(结合第4课)

1
2
3
4
5
6
7
(define prices (list 10 60 35 120))

(map (lambda (p)
(if (> p 50)
(string-append (number->string p) "元(贵!)")
(string-append (number->string p) "元(便宜!)")))
prices)

结果:'("10元(便宜!)" "60元(贵!)" "35元(便宜!)" "120元(贵!)")


第四部分:综合创作(8分钟)

终极任务:果果的冒险选择器

1
2
3
4
5
6
7
8
(define energy 80)     ; 果果的体力(0-100)
(define weather "sunny") ; 天气:"sunny" 或 "rainy"

(if (>= energy 70)
(if (string=? weather "sunny")
"去公园玩!🌞"
"在家画画!🎨")
"先休息一下~😴")

创作时间
让孩子们自己设计一个“冒险决策器”:
- 判断体力 + 天气 + 心情 → 决定今天做什么活动
- 或者:判断成绩 + 作业完成 → 决定周末玩多久游戏

老师巡回指导,鼓励多层if!


第五部分:总结与作业(2分钟)

核心口诀
if 就是选择魔法:
(if 判断题 对的时候做事 错的时候做事)

今天学会了: 1. 用 if 让程序会判断 2. 嵌套 if 做多层选择 3. 把 if 放进 map 批量判断

回家作业(简单又好玩): 1. 写一个“天气预报机”:输入天气("sunny"/"rainy"),输出今天穿什么衣服 2. 用 map + if(list 45 78 92 65 88) 打等级:90+ A,80+ B,70+ C,其他 D

果果编程第5课

下节课预告
“下节课学 compose 组合魔法 —— 把多个变形机器连成一个超级大机器!
比如:先翻倍 → 再加10 → 再变字符串 → 再加‘元’……一行代码搞定!”

板书总结:

1
2
3
4
如果魔法 if
(if 条件 真→做事A 假→做事B)
可以嵌套!
可以和 map 一起用 → 批量判断!

教学小贴士
- 多用前几课内容(变量、列表、图形、map)做例子,强化衔接
- 遇到错误就说“选择精灵迷路了,我们帮他指路!”
- 进度快的孩子可以试 cond 多条件写法(预告高级魔法)

果果们,今天你们都变成了会“选择”的小魔法师啦!✨

课程目标(45分钟)

学会 map:一次性对列表中所有元素做同样的操作


第一部分:问题引入(5分钟)

老师故事: > "上节课果果攒了零花钱 (list 10 20 30 40 50)。如果每笔钱都变成2倍,怎么办?" > > 一个一个改?太累了!今天教你 数字变形工厂 —— 一个咒语,整串数字一起变!


第二部分:认识 map(15分钟)

1. 基本语法(5分钟)

1
2
3
4
5
6
#lang racket

(define savings (list 10 20 30 40 50))

; 启动变形工厂:每个数字×2
(map (lambda (n) (* n 2)) savings)

运行结果'(20 40 60 80 100)

解释

  • map = 变形工厂启动
  • (lambda (n) (* n 2)) = 机器说明书:"拿数字n,输出n×2"
  • savings = 原材料

2. 动手练习(10分钟)

练习1:加10机器

1
2
(map (lambda (n) (+ n 10)) savings)
; 结果:'(20 30 40 50 60)

练习2:变字符串机器

1
2
(map (lambda (n) (number->string n)) savings)
; 结果:'("10" "20" "30" "40" "50")

练习3:加"元"机器

1
2
3
4
  (map (lambda (n) 
(string-append (number->string n) "元"))
savings)
; 结果:'("10元" "20元" "30元" "40元" "50元")

小挑战:把每个数字变成它的平方

1
2
(map (lambda (n) (* n n)) savings)
; 结果:'(100 400 900 1600 2500)


第三部分:string列表变形(15分钟)

1. 名字大变身(8分钟)

1
2
3
4
5
(define names (list "果果" "小明" "小红"))

; 加"同学"
(map (lambda (name) (string-append name "同学")) names)
; 结果:'("果果同学" "小明同学" "小红同学")

练习:加"你好,"在前面

1
2
(map (lambda (name) (string-append "你好," name)) names)
; 结果:'("你好,果果" "你好,小明" "你好,小红")

2. 重复魔法(7分钟)

1
2
3
; 每个名字重复3遍
(map (lambda (name) (string-append name name name)) names)
; 结果:'("果果果果果果" "小明小明小明" "小红小红小红")

练习:名字变"名字!"

1
2
(map (lambda (name) (string-append name "!")) names)
; 结果:'("果果!" "小明!" "小红!")


第四部分:综合练习(8分钟)

挑战:制作价格标签

1
2
3
4
5
6
7
8
(define prices (list 10 25 50 100))

; 目标:'("价格:10元" "价格:25元" ...)
(map (lambda (p)
(string-append "价格:"
(number->string p)
"元"))
prices)

进阶:如果价格>50,加"(贵)"

1
2
3
4
5
6
(map (lambda (p)
(if (> p 50)
(string-append (number->string p) "元(贵)")
(string-append (number->string p) "元")))
prices)
; 结果:'("10元" "25元" "50元" "100元(贵)")


第五部分:总结(2分钟)

核心口诀

map 就是批量变形术: (map 变形机器 列表) → 新列表

回家作业

  1. (list 1 2 3 4 5) 每个数字变成 "第X名"(X是数字)
  2. (list "苹果" "香蕉" "橘子") 每个变成 "我爱吃XX"

都考100分哦

果果编程第4课

下节课预告

"下节课学 compose 组合魔法 —— 把多个变形机器拼成一个超级机器!"


板书

1
2
3
4
5
6
7
8
map = 数字变形工厂
(map (lambda (x) ...) 列表)

常用变形:
(* x 2) → 翻倍
(+ x 10) → 加10
(number->string x) → 变文字
(string-append x "元") → 加单位

今天我们要学会的三件小魔法

  1. 把一堆数字串成一条“魔法数字链”(list)
  2. 从链子上“摘第一个数字”(car)和“看剩下的一串”(cdr)
  3. 请一个“不需要名字的临时小助手”帮忙算东西(lambda)

开场故事(5分钟)

老师拿着几颗糖果(或画在纸上的数字:10、20、30、40、50),串成一串:

“果果每周攒零花钱:
第一周10元,第二周20元,第三周30元……
如果我们想一次告诉电脑‘我攒了这些钱’,要怎么说呢?
一个一个写太麻烦啦!
今天我们学会用一个魔法,把它们全部串成一条‘魔法数字链’!”

第一关:学会串数字链(list)(10分钟)

在DrRacket里一起敲:

1
2
(define savings (list 10 20 30 40 50))
savings

屏幕显示: '(10 20 30 40 50)

老师解释: “这个括号里的东西,就是一条数字链!
就像糖葫芦:10连着20,20连着30……
电脑一下子就记住了整串!”

再试一个:

1
2
(define scores (list 85 90 95 100))
scores

问孩子: “你们猜猜,你们班的数学成绩如果串成一条链,会长什么样?”

小练习(让孩子自己敲): 1. 定义一条你喜欢的数字链,比如喜欢的零食价格
(例如:(define snacks (list 5 8 12 15)))

第二关:摘糖葫芦 —— car 和 cdr(15分钟)

老师指着糖葫芦串: “现在我想先吃第一颗(10元),用什么魔法?”

敲代码:

1
(car savings)

答案:10

老师说: “car 就是‘摘第一个’!
读作‘卡’——就像火车的第一节车厢。”

再敲:

1
(cdr savings)

答案:'(20 30 40 50)

老师说: “cdr 就是‘去掉第一个,给我剩下的整串’!
读作‘抠得儿’——就像把第一颗摘掉,剩下的一串还连着。”

层层剥开演示(一起数):

1
2
3
(car (cdr savings))          ; 20
(car (cdr (cdr savings))) ; 30
(car (cdr (cdr (cdr savings)))) ; 40

白板画过程(推荐):

1
2
3
4
5
原来链子:10 → 20 → 30 → 40 → 50
car: 10
cdr: 20 → 30 → 40 → 50
再car: 20
再cdr: 30 → 40 → 50

孩子动手练习(8-10分钟): 1. 用你刚才定义的链子,敲 (car 你的链子名字)
2. 敲 (cdr 你的链子名字)
3. 试试连续用 car 和 cdr,找出“第3个数字”

老师巡回:孩子卡住就一起数“第一个、第二个、第三个……”

第三关:请临时小助手 —— lambda(12分钟)

老师说: “现在我想把这条攒钱链上的每个数字都翻倍(×2),怎么办?
一个一个写太慢!
我们可以请一个‘临时小助手’,他不需要名字,只干一件事——把数字×2。”

先简单玩玩计算:

1
2
(* 10 2)   ; 20
(* 30 2) ; 60

再介绍 lambda:

1
(lambda (n) (* n 2))

老师解释: “这个就是临时小助手!
意思是:拿一个数字 n,把它×2。”

直接用他算:

1
2
((lambda (n) (* n 2)) 25)   ; 50
((lambda (n) (* n 2)) 7) ; 14

再试加法助手:

1
((lambda (x) (+ x 100)) 50)   ; 150

小练习(孩子敲): 1. 请一个临时小助手,把数字+10
试算:((lambda (y) (+ y 10)) 30) → 应该得40
2. 请一个临时小助手,把数字平方
试算:((lambda (z) (* z z)) 5) → 应该得25

果果编程第3课

结束总结 + 预告(3分钟)

老师一起回顾: “今天我们学会了三招: 1. list —— 把数字串成一条链
2. car —— 摘第一个,cdr —— 看剩下的链
3. lambda —— 请一个临时小助手帮忙算

下节课我们要把这些本领用在画图上!
可以一次把一串颜色变成一串气球,一串数字变成一串不同大小的星星……
准备好画生日礼物了吗?”

回家小作业(简单版)

  1. 写一条你自己的数字链(至少4个数字)
  2. 用 car 取出第一个,用 cdr 看剩下的
  3. 请一个 lambda 小助手,把某个数字×3,算一算

这个版本: - 每部分都只有1-2个核心代码 - 语言超级口语化,像聊天一样 - 完全不涉及图像,只用数字和糖葫芦比喻 - 练习量小,成功率高

如果觉得还是太快,可以把第三关(lambda)再拆成两节课,或者把作业再简化。
需要我再调整某一部分,或者准备下一节(图形+函数+map)的课程形式,随时告诉我!

课程目标

  • 学会什么是变量(就像小盒子,可以装数字、文字)
  • 用变量存“手表价格”和“我的钱”
  • 算一算:还要攒多久才能买最新款小天才电话手表?

最新款小天才手表价格(2026年1月参考)

小天才的旗舰款目前主要是 Z11(哪吒定制/少年版)或 Z10 升级系列,官方/主流电商价格大约在 2299–2399元 左右(不同颜色/IP联名款略有浮动)。我们用 2399元 作为最新款示例(很酷的视频通话 + 精准楼层定位 + 健康检测)。

完整代码(#lang at-exp racket + infix,全部英文变量)

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
#lang at-exp racket
(require infix)

; 第一步:定义变量(就像给小盒子贴标签,放东西进去)
(define watch_price 2399) ; 最新款小天才电话手表价格(元)
(define my_money 800) ; 我现在有的零花钱(元)
(define weekly_save 50) ; 每周能攒的钱(元)
(define weeks_needed 3)

; 第二步:用 infix 计算还要多少周(注意用 | | 包住有 - 的变量名)
@${ weeks_needed := (|watch_price| - |my_money|) / |weekly_save| }

; 直接 define 计算结果(最推荐给小朋友)
;(define weeks_needed
; (/ (- watch_price my_money) weekly_save))

; 显示结果
(display "最新小天才手表价格是: ")
(displayln watch_price)

(display "我现在有: ")
(displayln my_money)

(display "每周攒: ")
(displayln weekly_save)

(display "还要攒大约 ")
(display (ceiling weeks_needed)) ; ceiling = 向上取整,实际要买得攒够整周
(display " 周!")

; 额外:显示精确小数
(display " (精确是 ")
(display (exact->inexact weeks_needed))
(displayln " 周)")

运行后可能的输出(假设例子数字)

1
2
3
4
5
最新小天才手表价格是: 2399
我现在有: 800
每周攒: 50
还要攒大约 32 周!
(精确是 31.98 周)

解释给小朋友(上课可以说)

  • define 就像“创建一个小盒子,叫 watch_price,里面放 2399”
  • 变量名用英文(snake_case 或 camelCase 都可以),避免中文或 - 符号引起麻烦
  • := 是赋值(在 infix 里很方便)
  • ceiling 是“向上取整” → 因为你不能攒 31.98 周,必须等到第 32 周才能买哦!

练习小任务(给果果们)

  1. watch_price 改成 1999(Z9 或其他款),重新跑跑看要几周?
  2. 如果每周攒 100 元,会快很多吗?改改 weekly_save 试试!
  3. 想买更贵的 2899 元联名款?改价格再算算!
1
2
3
4
5
6
7
#lang racket

(define watch_price 2299) ; 最新款小天才电话手表价格(元)
(define my_money 800) ; 我现在有的零花钱(元)
(define weekly_save 150) ; 每周能攒的钱(元)

(/ (- watch_price my_money) weekly_save)
果果编程第2课

这样第二课就很有趣啦~既有变量概念,又贴近生活(小天才手表是小学生超爱的话题)。如果想加图片或改成其他价格,随时告诉我哦!加油果果们!🚀

45分钟课程设计 | 零基础入门

课程概述

  • 主题:用代码创造魔法图形
  • 目标:让每个孩子在45分钟内画出至少3个彩色图形
  • 核心理念:编程就是创造魔法

课程准备 (5分钟)

教师准备

  1. 投影仪:展示DrRacket界面
  2. 示例代码:准备好几个有趣的图形
  3. 奖励贴纸:星星、笑脸贴纸
  4. 提前安装:确保所有电脑有DrRacket

学生准备

  1. 打开电脑,启动DrRacket
  2. 准备好"魔法小手"(手指灵活活动)
  3. 给电脑起个有趣的名字(如"魔法盒子")

正式课程 (40分钟)

第一部分:认识魔法工具 (5分钟)

1. 趣味介绍 (2分钟)

1
2
3
"同学们好!今天我们要学习用代码创造魔法!
代码就像魔法咒语,电脑就像魔法棒,
我们一起念咒语,让电脑变出彩色图形!"

2. 认识DrRacket界面 (3分钟)

  • 上半区:"咒语书写区"(写代码的地方)
  • 下半区:"魔法展示区"(显示图形的地方)
  • 运行按钮:▶️ "魔法启动按钮"

简单演示

1
2
3
"看老师变魔法啦!"
点击运行 → 出现红色圆圈
孩子们:"哇!"

第二部分:第一个魔法咒语 (10分钟)

1. 学习第一个图形:圆 (5分钟)

1
2
3
#lang racket
(require 2htdp/image)
(circle 50 "solid" "red")

比喻教学法:

  • circle:"变圆圈"的咒语
  • 50:圆圈的大小(像气球大小)
  • "solid":实心的(不是空心的)
  • "red":红色(魔法颜色)

学生任务

1
2
3
1. 在咒语书写区输入这行代码
2. 点击"魔法启动按钮"(运行)
3. 看看你的第一个魔法图形!

2. 改变魔法参数 (5分钟)

小挑战

1
2
3
"试试看,你能变出:
1. 一个蓝色的大圆圈吗?
2. 一个黄色的空心圆圈吗?"

提示: - 颜色可以换:"blue""green""yellow" - 大小可以改:3080100 - 样式可以变:"outline"(空心)

第三部分:更多魔法图形 (15分钟)

1. 学习第二个图形:方形 (5分钟)

1
(square 60 "solid" "green")

学生任务

1
2
3
4
"现在我们来变方块!"
1. 输入上面的咒语
2. 运行看看
3. 试着做一个紫色的大方块

2. 学习第三个图形:三角形 (5分钟)

1
(triangle 70 "solid" "orange")

小比赛

1
2
3
4
5
"谁能在2分钟内变出:
1. 一个粉色三角形
2. 一个蓝色方形
3. 一个绿色圆圈
前三名获得魔法师贴纸!"

3. 图形组合游戏 (5分钟)

1
2
(beside (circle 30 "solid" "red")
(square 40 "solid" "blue"))

解释: - beside:让图形手拉手并排站 - 可以组合任意两个图形

创意任务

1
2
"创造你的第一个魔法图案:
把两个你喜欢的图形并排放!"

第四部分:小小魔法师创作 (10分钟)

1. 创作时间 (8分钟)

挑战任务

1
2
3
4
5
"现在你是小小魔法师了!
用你学到的三个咒语,创造:
1. 一个笑脸(用圆圈和三角形)
2. 或者一座小房子(方形+三角形)
3. 或者任何你想创造的魔法图案!

教师巡回指导: - 帮助有困难的同学 - 鼓励创意作品 - 拍照记录优秀作品

2. 作品展示 (2分钟)

1
2
3
4
5
"魔法展示时间!
请三位同学分享他们的作品:
1. 你创造了什么?
2. 你用了哪些咒语?
3. 你喜欢编程魔法吗?"

课程总结与延伸 (5分钟)

1. 魔法总结 (2分钟)

1
2
3
4
5
"今天我们学会了:
✨ 三个魔法咒语:circle、square、triangle
✨ 一个组合咒语:beside
✨ 学会了控制颜色、大小和样式
你们都是很棒的小魔法师!"

2. 家庭魔法作业 (2分钟)

1
2
3
4
"回家后可以:
1. 用今天学的咒语画一个彩虹(7个不同颜色的圆圈)
2. 画一个机器人(各种形状组合)
3. 教爸爸妈妈变一个简单的魔法图形"

3. 下节课预告 (1分钟)

1
2
3
4
"下节课更有趣!
我们要让图形动起来,变成动画魔法!
还会学习星星、多边形等更多咒语!
记得带着你的魔法笔记本哦!"

教学技巧与注意事项

保持趣味性

  • 使用孩子能理解的比喻
  • 多用鼓励性语言
  • 准备小奖品(贴纸、小星星)

技术要点

  1. 括号匹配:提醒孩子括号要成对出现
  2. 引号使用:颜色要用英文双引号
  3. 大小写:Racket区分大小写

常见问题预判

  1. 忘记引号red"red"
  2. 括号不匹配:检查开头结尾括号
  3. 单词拼错circlcircle

差异化教学

  • 进度快的:尝试starellipse等更多图形
  • 进度慢的:专注于掌握1-2个图形
  • 创意强的:鼓励设计复杂图案

板书/PPT设计

魔法咒语墙

1
2
3
4
5
魔法咒语列表:
1. (circle 大小 "solid/outline" "颜色")
2. (square 大小 "solid/outline" "颜色")
3. (triangle 大小 "solid/outline" "颜色")
4. (beside 图形1 图形2)

魔法颜色表

1
2
3
4
红色:"red"      蓝色:"blue"
绿色:"green" 黄色:"yellow"
紫色:"purple" 橙色:"orange"
粉色:"pink" 棕色:"brown"

评价标准

  • ✅ 能正确输入并运行至少1个图形
  • ✅✅ 能改变图形的颜色和大小
  • ✅✅✅ 能组合2个以上的图形
  • 🌟 有创意地设计出自己的图案

课程材料

  1. 打印的"魔法咒语卡片"(带回家复习)
  2. 学生作品展示墙(教室一角)
  3. "我是小魔法师"证书(下节课颁发)

第一课的核心目标:让孩子在第一次接触编程时,感受到"我能行"的成就感。通过简单、直观、有趣的图形编程,建立起对代码的亲近感和信心。45分钟后,每个孩子都应该带着笑容和至少一个自己创造的图形离开教室。

果果编程第一课

查看rpi日志级别

1
2
cat /proc/sys/kernel/printk
3 4 1 3

printk的四个参数详解

1
2
cat /proc/sys/kernel/printk
3 4 1 3

四个数字分别表示: 1. 控制台日志级别(3) - 只有级别≤3的消息会输出到控制台(串口/显示器) 2. 默认消息级别(4) - 没有指定级别的printk()使用这个级别 3. 最低允许级别(1) - 允许设置的最低级别 4. 启动时默认级别(3) - 内核启动早期的控制台级别

消息级别对应关系

1
2
3
4
5
6
7
8
9
级别    宏定义             说明
0 KERN_EMERG 紧急(系统可能崩溃)
1 KERN_ALERT 警报(必须立即处理)
2 KERN_CRIT 严重(严重情况)
3 KERN_ERR 错误(错误情况) ← 这是你能在控制台看到的最低级别
4 KERN_WARNING 警告
5 KERN_NOTICE 通知(普通但重要)
6 KERN_INFO 信息(普通消息) ← dev_info() 用这个级别
7 KERN_DEBUG 调试信息

对串口输出的影响

情况1:控制台日志级别=3

1
2
3
4
5
6
7
8
9
10
11
// 这些会输出到串口 ✓
pr_emerg() // 0级 ✓
pr_alert() // 1级 ✓
pr_crit() // 2级 ✓
pr_err() // 3级 ✓

// 这些不会输出到串口 ✗
pr_warn() // 4级 ✗
pr_notice() // 5级 ✗
pr_info() // 6级 ✗ - 包括 dev_info()
pr_debug() // 7级 ✗

情况2:如果设置 loglevel=7

1
2
3
4
5
6
7
8
9
// 所有消息都会输出到串口 ✓
pr_emerg() ✓
pr_alert() ✓
pr_crit() ✓
pr_err() ✓
pr_warn() ✓
pr_notice() ✓ // linux_banner 用这个级别
pr_info() ✓ // dev_info() 现在能看到了
pr_debug() ✓

加打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
diff --git a/init/main.c b/init/main.c
index 821df1f05e9c..05be14b81a4c 100644
--- a/init/main.c
+++ b/init/main.c
@@ -916,6 +916,11 @@ void start_kernel(void)
{
char *command_line;
char *after_dashes;
+ printk(KERN_EMERG "\n\n");
+ printk(KERN_EMERG "################################\n");
+ printk(KERN_EMERG "# RPi CUSTOM KERNEL ACTIVE #\n");
+ printk(KERN_EMERG "################################\n");
+ printk(KERN_EMERG "\n\n");

set_task_stack_end_magic(&init_task);
smp_setup_processor_id();

根据前面【树莓派学习】003-cross-compile

1
2
3
bear -- make O=build -j10 Image.gz modules dtbs
make O=build modules_install INSTALL_MOD_PATH=.
./install-rpi.sh

观察串口打印

1
2
3
4
5
6
7
8
9
10
minicom -b 115200 -D /dev/cu.usbserial-0001
[ 0.000000] ################################

[ 0.000k00] # RPi CUSTOM KERNEL ACTIVE #

[ 0.000000] ################################

[ 0.000000]

[ 0.000000]

mac pro要交叉编译树莓派linux的源码, 最好还是用ubuntu镜像。

ubuntu24.04 docker中编译, 参考dev

1
docker pull ghcr.io/cybrid-systems/dev

这里注意不要用mac mount的路径, 用docker中的文件系统路径。不然会有git工作区的问题。

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
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

'include/uapi/linux/netfilter/xt_CONNMARK.h'
'include/uapi/linux/netfilter/xt_connmark.h'
'include/uapi/linux/netfilter/xt_DSCP.h'
'include/uapi/linux/netfilter/xt_dscp.h'
'include/uapi/linux/netfilter/xt_MARK.h'
'include/uapi/linux/netfilter/xt_mark.h'
'include/uapi/linux/netfilter/xt_RATEEST.h'
'include/uapi/linux/netfilter/xt_rateest.h'
'include/uapi/linux/netfilter/xt_TCPMSS.h'
'include/uapi/linux/netfilter/xt_tcpmss.h'
'include/uapi/linux/netfilter_ipv4/ipt_ECN.h'
'include/uapi/linux/netfilter_ipv4/ipt_ecn.h'
'include/uapi/linux/netfilter_ipv4/ipt_TTL.h'
'include/uapi/linux/netfilter_ipv4/ipt_ttl.h'
'include/uapi/linux/netfilter_ipv6/ip6t_HL.h'
'include/uapi/linux/netfilter_ipv6/ip6t_hl.h'
'net/netfilter/xt_DSCP.c'
'net/netfilter/xt_dscp.c'
'net/netfilter/xt_HL.c'
'net/netfilter/xt_hl.c'
'net/netfilter/xt_RATEEST.c'
'net/netfilter/xt_rateest.c'
'net/netfilter/xt_TCPMSS.c'
'net/netfilter/xt_tcpmss.c'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+pooncelock+pombonce.litmus'
This warning occurs on a case-insensitive filesystem (like macOS's APFS or Windows' NTFS) when a Git repository contains files whose names differ only in case. Git detects these as potential conflicts because the filesystem treats them as identical.

1
2
3
git clone --depth=1 https://github.com/raspberrypi/linux.git
cd linux
apt install bc
1
2
3
4
mkdir build
make O=build bcm2711_defconfig
bear -- make O=build -j10 Image.gz modules dtbs
make O=build modules_install INSTALL_MOD_PATH=.

debug config

1
2
3
4
5
6
7
8
9
10
11
12
cat >> build/.config << 'EOF'
# Early Debug Configuration
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_EARLY_PRINTK=y
CONFIG_DEBUG_LL=y
CONFIG_DEBUG_UART_PL011=y
CONFIG_DEBUG_UART_PHYS=0x3f215040
CONFIG_DEBUG_UART_VIRT=0xf215040
CONFIG_DEBUG_UNCOMPRESS=y
CONFIG_DEBUG_LL_INCLUDE="debug/8250.S"
EOF

~/.config/clangd/config.yaml

1
2
CompileFlags:
Remove: [-mabi=lp64]

install-rpi.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 🎯 完整传输(rsync + sudo,权限OK)

KVER=$(ls build/lib/modules/ | head -1)
echo "模块版本: $KVER"

# 1. Image.gz(压缩内核,必须)
rsync -avz build/arch/arm64/boot/Image.gz pi@192.168.1.95:/boot/firmware/ --rsync-path="sudo rsync"

# 2. DTB(设备树,bcm2711/rpi5)
rsync -avz build/arch/arm64/boot/dts/broadcom/*.dtb pi@192.168.1.95:/boot/firmware/ --rsync-path="sudo rsync"

# 3. Overlays(驱动叠加,必须完整目录)
rsync -avz --delete build/arch/arm64/boot/dts/overlays/ pi@192.168.1.95:/boot/firmware/overlays/ --rsync-path="sudo rsync"

# 4. Modules(驱动模块,关键!)
rsync -avz --delete build/lib/modules/$KVER/ pi@192.168.1.95:/lib/modules/$KVER/ --rsync-path="sudo rsync"
ssh pi@192.168.1.95 "sudo depmod -a $KVER && echo 'depmod完成'"

# 5. 更新 + 重启
ssh pi@192.168.1.95 'cd ~/code/kernel-update && ./update-kernel.sh && sudo reboot'

~/code/kernel-update/backup-kernel.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
#!/bin/bash
# 保存到: ~/code/kernel-update/backup-kernel.sh

echo "=== 开始备份当前内核和配置 ==="

# 创建备份目录
sudo mkdir -p /boot/firmware/backup
BACKUP_DIR="/boot/firmware/backup/$(date +%Y%m%d_%H%M%S)"
sudo mkdir -p "$BACKUP_DIR"

# 备份内核文件
echo "备份内核文件..."
sudo cp /boot/firmware/kernel8.img "$BACKUP_DIR/"
sudo cp /boot/firmware/Image.gz "$BACKUP_DIR/" 2>/dev/null || true

# 备份配置文件
echo "备份配置文件..."
sudo cp /boot/firmware/config.txt "$BACKUP_DIR/"
sudo cp /boot/firmware/cmdline.txt "$BACKUP_DIR/"

# 备份设备树
echo "备份设备树..."
sudo cp /boot/firmware/*.dtb "$BACKUP_DIR/" 2>/dev/null || true
sudo cp -r /boot/firmware/overlays "$BACKUP_DIR/" 2>/dev/null || true

# 备份网络配置(重要!)
echo "备份网络配置..."
sudo cp -r /etc/netplan "$BACKUP_DIR/" 2>/dev/null || true
sudo cp -r /etc/NetworkManager "$BACKUP_DIR/" 2>/dev/null || true

echo "=== 备份完成 ==="
echo "备份位置: $BACKUP_DIR"
echo "当前备份列表:"
sudo ls -la "$BACKUP_DIR"

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
#!/bin/bash
# 保存到: ~/code/kernel-update/update-kernel.sh

echo "=== 开始更新内核 ==="

# 检查新内核文件是否存在
if [ ! -f "/boot/firmware/Image.gz" ]; then
echo "错误: 未找到新内核文件 /boot/firmware/Image.gz"
echo "请先传输新内核文件"
exit 1
fi

# 备份当前配置
./backup-kernel.sh

# 配置使用新内核
echo "配置使用新内核..."
sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.backup

# 在config.txt中启用新内核
if grep -q "kernel=Image.gz" /boot/firmware/config.txt; then
echo "新内核已配置"
else
# 注释原内核,启用新内核
sudo sed -i 's/^kernel=/#kernel=/' /boot/firmware/config.txt
echo "kernel=Image.gz" | sudo tee -a /boot/firmware/config.txt
fi

echo "=== 内核更新配置完成 ==="
echo "重启后生效: sudo reboot"
echo "验证命令: uname -r"

验证

1
2
3
4
5
6
7
8
9
10
11
12
% ssh pi@pi                                                                                             yuanqi@MacBook-Pro-2
Linux pi 6.12.57-v8+ #1 SMP PREEMPT Fri Nov 14 20:56:44 CST 2025 aarch64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Dec 7 14:02:19 2025 from 192.168.1.100
pi@pi:~ $ uname -r
6.12.57-v8+

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
#!/bin/bash
# 保存到: ~/code/kernel-update/restore-kernel.sh

echo "=== 内核恢复工具 ==="

# 查找最新的备份
LATEST_BACKUP=$(sudo ls -td /boot/firmware/backup/*/ | head -1)

if [ -z "$LATEST_BACKUP" ]; then
echo "错误: 未找到备份文件"
exit 1
fi

echo "找到备份: $LATEST_BACKUP"
echo "恢复内容:"
sudo ls -la "$LATEST_BACKUP"

read -p "确认恢复?(y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "取消恢复"
exit 0
fi

# 恢复内核文件
echo "恢复内核文件..."
sudo cp "$LATEST_BACKUP/kernel8.img" /boot/firmware/ 2>/dev/null || true
sudo cp "$LATEST_BACKUP/Image.gz" /boot/firmware/ 2>/dev/null || true

# 恢复配置文件
echo "恢复配置文件..."
sudo cp "$LATEST_BACKUP/config.txt" /boot/firmware/
sudo cp "$LATEST_BACKUP/cmdline.txt" /boot/firmware/

# 恢复设备树
echo "恢复设备树..."
sudo cp "$LATEST_BACKUP"/*.dtb /boot/firmware/ 2>/dev/null || true
sudo cp -r "$LATEST_BACKUP/overlays" /boot/firmware/ 2>/dev/null || true

# 恢复网络配置(如果需要)
if [ -d "$LATEST_BACKUP/netplan" ]; then
echo "恢复网络配置..."
sudo cp -r "$LATEST_BACKUP/netplan" /etc/
sudo netplan apply
fi

echo "=== 恢复完成 ==="
echo "请重启: sudo reboot"
0%