chore 不确定性有问题 真喜欢用无序列表啊
@@ -305,6 +305,15 @@ export default defineConfig({
|
|||||||
{ text: '4.3.2.2项目:扫雷,骑士与流氓问题', link: '/4.人工智能/4.3.2.2项目:扫雷,骑士与流氓问题' },
|
{ text: '4.3.2.2项目:扫雷,骑士与流氓问题', link: '/4.人工智能/4.3.2.2项目:扫雷,骑士与流氓问题' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: '4.3.3不确定性问题',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: '4.3.3不确定性问题', link: '/4.人工智能/4.3.3不确定性问题' },
|
||||||
|
{ text: '4.3.3.1程序示例', link: '/4.人工智能/4.3.3.1程序示例' },
|
||||||
|
{ text: '4.3.3.2项目:遗传', link: '/4.人工智能/4.3.3.2项目:遗传' },
|
||||||
|
]
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ text: '4.4FAQ:常见问题', link: '/4.人工智能/4.4FAQ:常见问题' },
|
{ text: '4.4FAQ:常见问题', link: '/4.人工智能/4.4FAQ:常见问题' },
|
||||||
@@ -558,6 +567,8 @@ export default defineConfig({
|
|||||||
items: [
|
items: [
|
||||||
{ text: '5.富有生命的嵌入式', link: '/5.富有生命的嵌入式/5.富有生命的嵌入式' },
|
{ text: '5.富有生命的嵌入式', link: '/5.富有生命的嵌入式/5.富有生命的嵌入式' },
|
||||||
{ text: '5.1嵌入式是什么?可以吃吗?', link: '/5.富有生命的嵌入式/5.1嵌入式是什么?可以吃吗?' },
|
{ text: '5.1嵌入式是什么?可以吃吗?', link: '/5.富有生命的嵌入式/5.1嵌入式是什么?可以吃吗?' },
|
||||||
|
{ text: '5.2New meaning of C', link: '/5.富有生命的嵌入式/5.2New meaning of C' },
|
||||||
|
{ text: '5.3还玩裸机?上操作系统!', link: '/5.富有生命的嵌入式/5.3还玩裸机?上操作系统!' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
53
4.人工智能/4.3.3.1程序示例.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 程序示例
|
||||||
|
::: tip
|
||||||
|
阅读程序,然后“玩一玩”程序!
|
||||||
|
|
||||||
|
完成习题
|
||||||
|
:::
|
||||||
|
|
||||||
|
本节代码不做额外梳理,[不确定性问题](./4.3.3%E4%B8%8D%E7%A1%AE%E5%AE%9A%E6%80%A7%E9%97%AE%E9%A2%98.md) 中已有解释。
|
||||||
|
|
||||||
|
## Quiz
|
||||||
|
|
||||||
|
1. 考虑一副标准的 52 张牌,在四种花色(梅花、方块、红心、黑桃)中各有 13 种牌值(A、K、Q、J 和 2-10)。如果随机抽出一张牌,它是黑桃或 2 的概率是多少?
|
||||||
|
1. About 0.019
|
||||||
|
2. About 0.077
|
||||||
|
3. About 0.17
|
||||||
|
4. About 0.25
|
||||||
|
5. About 0.308
|
||||||
|
6. About 0.327
|
||||||
|
7. About 0.5
|
||||||
|
8. None of the above
|
||||||
|
2. 想象一下,抛出两枚硬币,每枚硬币都有正面和反面,50% 的时间出现正面,50% 的时间出现反面。抛出这两枚硬币后,其中一枚是正面,另一枚是反面的概率是多少?
|
||||||
|
1. 0
|
||||||
|
2. 0.125
|
||||||
|
3. 0.25
|
||||||
|
4. 0.375
|
||||||
|
5. 0.5
|
||||||
|
6. 0.625
|
||||||
|
7. 0.75
|
||||||
|
8. 0.875
|
||||||
|
9. 1
|
||||||
|
3. 回答关于贝叶斯网络的问题,问题如下:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
以下哪句话是真的?
|
||||||
|
|
||||||
|
1. 假设我们知道有轨道维护,那么是否有雨并不影响列车准时到达的概率。
|
||||||
|
2. 假设我们知道有雨,那么是否有轨道维修并不影响列车准时到达的概率。
|
||||||
|
3. 假设我们知道火车是准时的,是否有雨会影响到赴约的概率。
|
||||||
|
4. 假设我们知道火车是准时的,那么是否有轨道维修并不影响赴约的概率。
|
||||||
|
5. 假设我们知道有轨道维护,那么是否有雨并不影响参加约会的概率。
|
||||||
|
4. 两家工厂--A 厂和 B 厂--设计用于手机的电池。A 厂生产 60% 的电池,B 厂生产另外 40%。A 厂 2% 的电池有缺陷,B 厂 4% 的电池有缺陷。一个电池既由 A 厂生产又有缺陷的概率是多少?
|
||||||
|
1. 0.008
|
||||||
|
2. 0.012
|
||||||
|
3. 0.024
|
||||||
|
4. 0.028
|
||||||
|
5. 0.02
|
||||||
|
6. 0.06
|
||||||
|
7. 0.12
|
||||||
|
8. 0.2
|
||||||
|
9. 0.429
|
||||||
|
10. 0.6
|
||||||
|
11. None of the above
|
||||||
93
4.人工智能/4.3.3.2项目:遗传.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# 项目:遗传
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
我们为你提供了一个简单有趣的项目,帮助你进行知识巩固,请认真阅读文档内容。
|
||||||
|
|
||||||
|
如果你卡住了,请记得回来阅读文档,或请求身边人的帮助。
|
||||||
|
:::
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
- GJB2 基因的突变版本是导致新生儿听力障碍的主要原因之一。每个人都携带两个版本的基因,因此每个人都有可能拥有 0、1 或 2 个听力障碍版本的 GJB2 基因。不过,除非一个人接受基因测试,否则要知道一个人拥有多少个变异的 GJB2 基因并不那么容易。这是一些 "隐藏状态":具有我们可以观察到的影响(听力损伤)的信息,但我们不一定直接知道。毕竟,有些人可能有 1 或 2 个突变的 GJB2 基因,但没有表现出听力障碍,而其他人可能没有突变的 GJB2 基因,但仍然表现出听力障碍。
|
||||||
|
- 每个孩子都会从他们的父母那里继承一个 GJB2 基因。如果父母有两个变异基因,那么他们会将变异基因传给孩子;如果父母没有变异基因,那么他们不会将变异基因传给孩子;如果父母有一个变异基因,那么该基因传给孩子的概率为 0.5。不过,在基因被传递后,它有一定的概率发生额外的突变:从导致听力障碍的基因版本转变为不导致听力障碍的版本,或者反过来。
|
||||||
|
- 我们可以尝试通过对所有相关变量形成一个贝叶斯网络来模拟所有这些关系,就像下面这个网络一样,它考虑了一个由两个父母和一个孩子组成的家庭。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 家庭中的每个人都有一个 `Gene` 随机变量,代表一个人有多少个特定基因(例如,GJB2 的听力障碍版本):一个 0、1 或 2 的值。家族中的每个人也有一个 `Trait` 随机变量,它是 `yes` 或 `no`,取决于该人是否表达基于该基因的性状(例如,听力障碍)。从每个人的 `Gene` 变量到他们的 `Trait` 变量之间有一个箭头,以编码一个人的基因影响他们具有特定性状的概率的想法。同时,也有一个箭头从母亲和父亲的 `Gene` 随机变量到他们孩子的 `Gene` 随机变量:孩子的基因取决于他们父母的基因。
|
||||||
|
- 你在这个项目中的任务是使用这个模型对人群进行推断。给出人们的信息,他们的父母是谁,以及他们是否具有由特定基因引起的特定可观察特征(如听力损失),你的人工智能将推断出每个人的基因的概率分布,以及任何一个人是否会表现出有关特征的概率分布。
|
||||||
|
|
||||||
|
## 理解
|
||||||
|
|
||||||
|
- 打开 `data/family0.csv`,看看数据目录中的一个样本数据集(你可以在文本编辑器中打开,或者在 Google Sheets、Excel 或 Apple Numbers 等电子表格应用程序中打开)。注意,第一行定义了这个 CSV 文件的列:`name`, `mother`, `father`, 和 `trait`。下一行表明 Harry 的母亲是 Lily,父亲是 James,而 `Trait` 的空单元格意味着我们不知道 Harry 是否有这种性状。同时,James 在我们的数据集中没有列出父母(如母亲和父亲的空单元格所示),但确实表现出了性状(如 `Trait` 单元格中的 1 所示)。另一方面,Lily 在数据集中也没有列出父母,但没有表现出这种性状(如 `Trait` 单元格中的 0 表示)。
|
||||||
|
- 打开 `heredity.py`,首先看一下 `PROBS` 的定义。`PROBS` 是一个包含若干常数的字典,代表各种不同事件的概率。所有这些事件都与一个人拥有多少个特定的突变基因,以及一个人是否基于该基因表现出特定的性状有关。这里的数据松散地基于 GJB2 基因的听力障碍版本和听力障碍性状的概率,但通过改变这些值,你也可以用你的人工智能来推断其他的基因和性状!
|
||||||
|
- 首先,`PROBS["gene"]` 代表了该基因的无条件概率分布(即如果我们对该人的父母一无所知的概率)。根据分布代码中的数据,在人群中,有 1% 的机会拥有该基因的 2 个副本,3% 的机会拥有该基因的 1 个副本,96% 的机会拥有该基因的零副本。
|
||||||
|
- 接下来,`PROBS["trait"]` 表示一个人表现出某种性状(如听力障碍)的条件概率。这实际上是三个不同的概率分布:基因的每个可能值都有一个。因此,`PROBS["trait"][2]` 是一个人在有两个突变基因的情况下具有该特征的概率分布:在这种情况下,他们有 65% 的机会表现出该特征,而有 35% 的机会不表现出该特征。同时,如果一个人有 0 个变异基因,他们有 1% 的机会表现出该性状,99% 的机会不表现出该性状。
|
||||||
|
- 最后,`PROBS["mutation"]` 是一个基因从作为相关基因突变为不是该基因的概率,反之亦然。例如,如果一个母亲有两个变异基因,并因此将其中一个传给她的孩子,就有 1% 的机会突变为不再是变异基因。相反,如果一个母亲没有任何变异基因,因此没有把变异基因传给她的孩子,但仍有 1% 的机会突变为变异基因。因此,即使父母双方都没有变异基因,他们的孩子也可能有 1 个甚至 2 个变异基因。
|
||||||
|
- 最终,你计算的概率将以 `PROBS` 中的这些数值为基础。
|
||||||
|
- 现在,看一下 `main` 函数。该函数首先将数据从一个文件加载到一个字典 `people` 中。`people` 将每个人的名字映射到另一个包含他们信息的字典中:包括他们的名字,他们的母亲(如果数据集中有一个母亲),他们的父亲(如果数据集中有一个父亲),以及他们是否被观察到有相关的特征(如果有则为 `True`,没有则为 `False`,如果我们不知道则为 `None`)。
|
||||||
|
- 接下来,`main` 中定义了一个字典 `probabilities`,所有的概率最初都设置为 0。这就是你的项目最终要计算的内容:对于每个人,你的人工智能将计算他们有多少个变异基因的概率分布,以及他们是否具有该性状。例如,`probabilities["Harry"]["gene"][1]` 将是 Harry 有 1 个变异基因的概率,而 `probabilities["Lily"]["trait"][False]` 将是 Lily 没有表现出该性状的概率。
|
||||||
|
- 如果不熟悉的话,这个 `probabilities` 字典是用 [python 字典](https://www.python.org/dev/peps/pep-0274/)创建的,在这种情况下,它为我们的 `people` 中的每个 `person` 创建一个键/值对。
|
||||||
|
- 最终,我们希望根据一些证据来计算这些概率:鉴于我们知道某些人有或没有这种特征,我们想确定这些概率。你在这个项目中的任务是实现三个函数来做到这一点: `joint_probability` 计算一个联合概率,`update` 将新计算的联合概率添加到现有的概率分布中,然后 `normalize` 以确保所有概率分布最后和为 1。
|
||||||
|
|
||||||
|
## 明确
|
||||||
|
|
||||||
|
- 完成 `joint_probability`、`update` 和 `normalize` 的实现。
|
||||||
|
- `joint_probability` 函数应该接受一个 `people` 的字典作为输入,以及关于谁拥有多少个变异基因,以及谁表现出该特征的数据。该函数应该返回所有这些事件发生的联合概率。
|
||||||
|
|
||||||
|
- 该函数接受四个数值作为输入:`people`, `one_gene`, `two_genes`, 和 `have_trait`。
|
||||||
|
|
||||||
|
- `people` 是一个在 "理解"一节中描述的人的字典。键代表名字,值是包含 `mother` 和 `father` 键的字典。你可以假设 `mother` 和 `father` 都是空白的(数据集中没有父母的信息),或者 `mother` 和 `father` 都会指代 `people` 字典中的其他人物。
|
||||||
|
- `one_gene` 是一个集合,我们想计算所有集合元素有一个变异基因的概率。
|
||||||
|
- `two_genes` 是一个集合,我们想计算所有集合元素有两个变异基因的概率。
|
||||||
|
- `have_trait` 是一个集合,我们想计算所有集合元素拥有该性状的概率。
|
||||||
|
- 对于不在 `one_gene` 或 t `wo_genes` 中的人,我们想计算他们没有变异基因的概率;对于不在 `have_trait` 中的人,我们想计算他们没有该性状的概率。
|
||||||
|
|
||||||
|
- 例如,如果这个家庭由 Harry、James 和 Lily 组成,那么在 `one_gene = {"Harry"}`、`two_genes = {"James"}` 和 `trait = {"Harry"、"James"}` 的情况下调用这个函数,应该计算出 Lily 没有变异基因、Harry 拥有一个变异基因、James 拥有两个变异基因、Harry 表现出该性状、James 表现出该性状和 Lily 没有表现出该性状的联合概率。
|
||||||
|
- 对于数据集中没有列出父母的人,使用概率分布 `PROBS["gene"]` 来确定他们有特定数量基因的概率。
|
||||||
|
- 对于数据集中有父母的人来说,每个父母都会把他们的两个基因中的一个随机地传给他们的孩子,而且有一个 `PROBS["mutation"]` 的机会,即它会发生突变(从变异基因变成正常基因,或者相反)。
|
||||||
|
- 使用概率分布 `PROBS["trait"]` 来计算一个人具有或不具有形状的概率。
|
||||||
|
- `update` 函数将一个新的联合分布概率添加到 `probabilities` 中的现有概率分布中。
|
||||||
|
|
||||||
|
- 该函数接受五个值作为输入:`probabilities`, `one_gene`, `two_genes`, `have_trait`, 和 `p`。
|
||||||
|
|
||||||
|
- `probabilities` 是一个在 "理解 "部分提到的字典。每个人都被映射到一个 `"gene"` 分布和一个 `"trait"` 分布。
|
||||||
|
- `one_gene` 是一个集合,我们想计算所有集合元素有一个变异基因的概率。
|
||||||
|
- `two_genes` 是一个集合,我们想计算所有集合元素有两个变异基因的概率。
|
||||||
|
- `have_trait` 是一个集合,我们想计算所有集合元素拥有该性状的概率。
|
||||||
|
- `p` 是联合分布的概率。
|
||||||
|
- 对于概率中的每个人,该函数应该更新 `probabilities[person]["gene"]` 分布和 `probabilities[person]["trait"]` 分布,在每个分布中的适当数值上加上 `p`。所有其他数值应保持不变。
|
||||||
|
- 例如,如果"Harry"同时出现在 `two_genes` 和 `have_trait` 中,那么 `p` 将被添加到 `probabilities["Harry"]["gene"][2]` 和 `probabilities["Harry"]["trait"][True]`。
|
||||||
|
- 该函数不应返回任何值:它只需要更新 `probabilities` 字典。
|
||||||
|
- `normalize` 函数更新 `probabilities` 字典,使每个概率分布被归一化(即和为 1,相对比例相同)。
|
||||||
|
|
||||||
|
- 该函数接受一个单一的值:`probabilities`。
|
||||||
|
|
||||||
|
- `probabilities` 是一个在"理解"部分提到的字典。每个人都被映射到一个 `"gene"` 分布和一个 `"trait"` 分布。
|
||||||
|
- 对于 `probabilities` 中每个人的两个分布,这个函数应该将该分布归一化,使分布中的数值之和为 1,分布中的相对数值是相同的。
|
||||||
|
- 例如,如果 `probabilities["Harry"]["trait"][True]` 等于 `0.1`,概率 `probabilities["Harry"]["trait"][False]` 等于 `0.3`,那么你的函数应该将前一个值更新为 `0.25`,后一个值更新为 `0.75`: 现在数字之和为 1,而且后一个值仍然比前一个值大三倍。
|
||||||
|
- 该函数不应返回任何值:它只需要更新 `probabilities` 字典。
|
||||||
|
- 除了规范中要求你实现的三个函数外,你不应该修改 `heredity.py` 中的任何其他东西,尽管你可以编写额外的函数和/或导入其他 Python 标准库模块。如果熟悉的话,你也可以导入 `numpy` 或 `pandas`,但是你不应该使用任何其他第三方 Python 模块。
|
||||||
|
|
||||||
|
## 一个联合概率例子
|
||||||
|
|
||||||
|
- 为了帮助你思考如何计算联合概率,我们在下面附上一个例子。
|
||||||
|
- 请考虑以下 `people` 的值:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
'Harry': {'name': 'Harry', 'mother': 'Lily', 'father': 'James', 'trait': None},
|
||||||
|
'James': {'name': 'James', 'mother': None, 'father': None, 'trait': True},
|
||||||
|
'Lily': {'name': 'Lily', 'mother': None, 'father': None, 'trait': False}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 这里我们将展示 `joint_probability(people, {"Harry"}, {"James"}, {"James"})` 的计算。根据参数,`one_gene` 是 `{"Harry"}`,`two_genes` 是 `{"James"}`,而 `has_trait` 是 `{"James"}`。因此,这代表了以下的概率:Lily 没有变异基因,不具有该性状;Harry 有一个变异基因,不具有该性状;James 有 2 个变异基因,具有该性状。
|
||||||
|
- 我们从 Lily 开始(我们考虑人的顺序并不重要,只要我们把正确的数值乘在一起,因为乘法是可交换的)。Lily 没有变异基因,概率为 `0.96`(这就是 `PROBS["gene"][0]`)。鉴于她没有变异基因,她没有这个性状的概率为 `0.99`(这是 `PROBS["trait"][0][False]`)。因此,她没有变异基因且没有该性状的概率是 `0.96*0.99=0.9504`。
|
||||||
|
- 接下来,我们考虑 James。James 有 2 个变异基因,概率为 `0.01`(这是 `PROBS["gene"][2]`)。鉴于他有 2 个变异基因,他确实具有该性状的概率为 `0.65`。因此,他有 2 个变异基因并且他确实具有该性状的概率是 `0.01*0.65=0.0065`。
|
||||||
|
- 最后,我们考虑 Harry。Harry 有 1 个变异基因的概率是多少?有两种情况可以发生。要么他从母亲那里得到这个基因,而不是从父亲那里,要么他从父亲那里得到这个基因,而不是从母亲那里。他的母亲 Lily 没有变异基因,所以 Harry 会以 `0.01` 的概率从他母亲那里得到这个基因(这是 `PROBS["mutation"]`),因为从他母亲那里得到这个基因的唯一途径是基因突变;相反,Harry 不会从他母亲那里得到这个基因,概率是 `0.99`。他的父亲 James 有 2 个变异基因,所以 Harry 会以 `0.99` 的概率从他父亲那里得到这个基因(这是 `1-PROBS["mutation"]`),但会以 `0.01` 的概率从他母亲那里得到这个基因(突变的概率)。这两种情况加在一起可以得到 `0.99*0.99+0.01*0.01=0.9802`,即 Harry 有 1 个变异基因的概率。
|
||||||
|
- 考虑到 Harry 有 1 个变异基因,他没有该性状的概率是 `0.44`(这是 `PROBS["trait"][1][false]`)。因此,哈利有 1 个变异基因而没有该性状的概率是 `0.9802 * 0.44 = 0.431288`。
|
||||||
|
- 因此,整个联合概率是三个人中每个人的所有这些数值相乘的结果: `0.9504 * 0.0065 * 0.431288 = 0.0026643247488`。
|
||||||
|
|
||||||
|
## 提示
|
||||||
|
|
||||||
|
- 回顾一下,要计算多个事件的联合概率,你可以通过将这些概率相乘来实现。但请记住,对于任何孩子来说,他们拥有一定数量的基因的概率是以他们的父母拥有什么基因为条件的。
|
||||||
1049
4.人工智能/4.3.3不确定性问题.md
Normal file
BIN
4.人工智能/static/AptYbb5MZoylvex7LvPcSqivnef.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
4.人工智能/static/CreObGAg4oXB0oxe2hMcQbYZnAc.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
4.人工智能/static/E0TtbfgiCoV2dtxbbPHcjPgXnQe.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
4.人工智能/static/FNyab3RWQo3EA8xu8T7cyLwhnyh.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
4.人工智能/static/FnyrbYSEWohimaxIYPSchotGnse.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
4.人工智能/static/GKc6be6ueopUYZxxQg4cS4AVnmb.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
4.人工智能/static/GqlRbfW7Yom5a9xKCBHckMBuniF.png
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
4.人工智能/static/Ilj3bPKuwo0l6Dx13rZcVXfenOb.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
4.人工智能/static/KsELbuMTCoKZkGxU9U5czQpanKg.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
4.人工智能/static/MkZ6bIPFroAm3lxzLydcsn5QnNg.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
4.人工智能/static/MrP0b2FbXofDsOxgnmncufUynAB.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
4.人工智能/static/PUesbhgsFoiucAxWBKYcUUU3nMd.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
4.人工智能/static/VBGxbrNgAovuKXxnTKYcm7UinFd.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
4.人工智能/static/Vr96bdSafoV4kBxJ3x2cAU0TnOg.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
4.人工智能/static/XBghbKBaVoz0C4xa85rch804ngd.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
4.人工智能/static/XZfhbR6sBorTI9x7hVVchGLUn3b.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
4.人工智能/static/Y8EbbcnUsoHHlFxHCrGcIUDNn0f.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
161
5.富有生命的嵌入式/5.2New meaning of C.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# New meaning of C
|
||||||
|
|
||||||
|
> Author:肖扬
|
||||||
|
|
||||||
|
在上一篇文章中,我们介绍了嵌入式相关的产品,其中芯片充当着及其重要的作用,本篇我们将以孵化器实验室的部分考核流程与教程作为参考,并且加以一定的概念解析,逐步地为大家解开单片机裸机开发的面纱。
|
||||||
|
|
||||||
|
PS:在学习此模块之前,我希望你们能熟练掌握 C 语言的基础知识,特别是指针方面的内容。
|
||||||
|
|
||||||
|
如果你还没有完成 C 语言的学习,可以参考此文章:[3.4C 语言](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.4C%E8%AF%AD%E8%A8%80.md)
|
||||||
|
|
||||||
|
## STC89C52--一款入门级的芯片
|
||||||
|
|
||||||
|
### 相关介绍
|
||||||
|
|
||||||
|
Intel 的 STC89C51\C52 系列早在上世纪 80 年代就已经广泛运用,考虑到其精简但较为充足的硬件资源以及其低廉的价格,笔者十分建议可以从 51 单片机系列开始去接触整个嵌入式行业(因为你需要知道自己适不适合进入到嵌入式行业,大学本就是一个试错的过程,而低廉的价格使得其试错成本变得很低)
|
||||||
|
|
||||||
|
以下是比较推荐的学习路线:
|
||||||
|
|
||||||
|
1、购买 51 单片机:[https://m.tb.cn/h.UuGJR5G?tk=kN94dm040JX](https://m.tb.cn/h.UuGJR5G?tk=kN94dm040JX) CZ0001 「普中 51 单片机学习板开发板 stc89c52 单片机实验板 C51 单片机 diy 套件」
|
||||||
|
|
||||||
|
2、推荐学习视频:【51 单片机入门教程-2020 版 程序全程纯手打 从零开始入门】[https://b23.tv/KmaWgUK](https://b23.tv/KmaWgUK)
|
||||||
|
|
||||||
|
3、相关学习资料:
|
||||||
|
|
||||||
|
软件安装包、开发板资料、课件及程序源码百度网盘链接:
|
||||||
|
|
||||||
|
[https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng](https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng) 提取码:gdzf,链接里压缩包的解压密码:51 ,如果打不开请复制链接到浏览器再打开
|
||||||
|
|
||||||
|
### 一些简单的任务
|
||||||
|
|
||||||
|
咳咳,摆烂一下,放一下 22 年实验室的培养计划(<del>只给第一期,不能再多了</del>):
|
||||||
|
|
||||||
|
在学习完这些内容后,你可以尝试如下的工程实践:
|
||||||
|
|
||||||
|
## 寄存器(VERRY--IMPORTANT!!!)
|
||||||
|
|
||||||
|
相信学完 C51 的内容,你已经对寄存器有了一定的了解。
|
||||||
|
|
||||||
|
但是由于其重要性,以及为了巩固各位的基础知识,防止出现有些人学了 A 只会 A,学了 B 只会 B,而不会 CDEF...的现象,所以这里我必须要着重重新讲解一下寄存器的概念。
|
||||||
|
|
||||||
|
在 C 语言中我们有 int、short、long 等变量,实际上 C 语言中还可以定义一个寄存器变量(register)。
|
||||||
|
|
||||||
|
那么为什么需要寄存器呢,或者说我们为什么需要一个寄存器变量呢。
|
||||||
|
|
||||||
|
这里我不从计算机组成原理或者微机接口与技术的概念入手,而是举一个简单的例子:
|
||||||
|
|
||||||
|
如果我们在图书馆上准备看书,去获取知识,此时我们是 CPU、书则是数据。
|
||||||
|
|
||||||
|
如果我们去图书馆里的书架上拿书并观看,则需要:走到对应书架-拿书(获取数据)-回到书桌,这需要花费相当一部分的时间,此时硬盘相当于书架;如果我们直接拿书桌上的书,则相对速度会快很多,此时书桌相当于主存;如果我们手上就有一本书,那么我们低头就可以看到,手就相当于寄存器。所以,寄存器是 CPU 内部用来存放数据的一些小型的存储区域,用来暂时存放参与运算的数据和运算结果以及一些 CPU 运行所需要的信息。
|
||||||
|
|
||||||
|
以我举例而言,<strong>可见寄存器获得数据的速度会快于主存与硬盘,而存储数据的大小将会小于主存与硬盘</strong>,如果这块不清楚的话也可以去看 也许你会用上的基础知识 中的存储器知识部分。
|
||||||
|
|
||||||
|
而从汇编语言的角度来讲(此为 Intel 的汇编语法):
|
||||||
|
|
||||||
|
```asm
|
||||||
|
WAIT_FOR_TIMER:
|
||||||
|
MOV A, LED_COUNT ;读取当前亮灯的编号
|
||||||
|
CJNE A, #00H, NOT_FIRST_LED ;如果不是第一个灯,则跳转到NOT_FIRST_LED标签
|
||||||
|
MOV A, #03H ;如果是第一个灯,则将延时设为3秒
|
||||||
|
SJMP DELAY ;跳转到延时
|
||||||
|
NOT_FIRST_LED:
|
||||||
|
.......
|
||||||
|
DELAY:
|
||||||
|
.......
|
||||||
|
;此汇编代码块中,累加器A作为一个常用的特殊寄存器,充当着暂时存储数据信息的作用
|
||||||
|
;存储LED_COUNT的编号并用于比较大小,存储所需延时时间并用于DELAY块
|
||||||
|
```
|
||||||
|
|
||||||
|
```asm
|
||||||
|
ORG 0BH
|
||||||
|
PUSH ACC ; 保存寄存器状态
|
||||||
|
PUSH PSW
|
||||||
|
......
|
||||||
|
POP PSW ; 恢复寄存器状态
|
||||||
|
POP ACC
|
||||||
|
RETI ; 返回中断
|
||||||
|
;此汇编代码块用于基本的中断处理,我们需要保存ACC、PSW的状态来维持中断以及中断后程序的正常进行
|
||||||
|
```
|
||||||
|
|
||||||
|
以上简单例举了寄存器的一般作用,以汇编语言出发去讲的原因是:它能有效地展现底层代码的工作原理,既不会像机器语言那样只用 0、1 表示的晦涩难懂,又不会像高级语言那般难以直观地看到底层的工作方式。但是,我们做嵌入式入门开发的过程中并不会让你直接去写汇编语言,而是以最基础的 C 语言(相比汇编而言,C 在功能上、结构性、可读性、可维护性上有明显的优势),通过 Keil、IAR 等拥有交叉编译器的 C 语言软件开发系统来完成高级语言、汇编语言、机器语言的转码工作,从而通过 C 语言的编写来控制单片机等嵌入式系统的开发。
|
||||||
|
|
||||||
|
而抽象层面的 C 代码需要通过访问寄存器来直接控制硬件。所以在嵌入式开发的过程中,C 有了特殊的含义:<strong>C 里的数字代表的可能只是一个地址或者一个数据,但是在嵌入式开发里,这样一个数字也可以代表着一种寄存器状态。</strong>
|
||||||
|
|
||||||
|
下面引入一个 STM32F1 系列的 GPIO 部分寄存器图(来源正点原子提供的 F1 参考手册):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果我们想做一个简单的实验-驱动一个 LED 灯(假设此 LED 灯以 PB5 为输出驱动口),在对相应的 RCC 时钟等配置之外,最重要的是对相应的 GPIO 口的配置,首先我们查阅其寄存器的物理起始地址:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
可见 GPIO 外设通过 APB2 总线进行地址定位与传输数据的,所以我们要控制 PB5 的话首先需要定位到对应的地址:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define PERIPH_BASE ((uint32_t)0x40000000) //外设基址
|
||||||
|
|
||||||
|
#define APB1PERIPH_BASE PERIPH_BASE
|
||||||
|
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2基址
|
||||||
|
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
|
||||||
|
|
||||||
|
#define AFIO_BASE (APB2PERIPH_BASE + 0x0000)
|
||||||
|
#define EXTI_BASE (APB2PERIPH_BASE + 0x0400)
|
||||||
|
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
|
||||||
|
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)//GPIOB基址,计算可得0x40010C00
|
||||||
|
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
|
||||||
|
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
|
||||||
|
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
|
||||||
|
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
|
||||||
|
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
|
||||||
|
//APB2还有相关定时器的基址,这里就不再展示
|
||||||
|
|
||||||
|
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
|
||||||
|
```
|
||||||
|
|
||||||
|
根据如上提供的 CRL、ODR 寄存器的功能映射,要使得 PB5 推挽输出且初始电平输出为高,则需要:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void LED_Init(void)
|
||||||
|
{
|
||||||
|
RCC->APB2ENR|=1<<3; //GPIOB的时钟使能,只有使能对应的时钟后GPIO才能正常工作
|
||||||
|
|
||||||
|
GPIOB->CRL&=0XFF0FFFFF; //由图可知,CRL的第20-23位控制5口,此举是对第20-23位清零
|
||||||
|
GPIOB->CRL|=0X00300000; //此举是对第20-23位赋值0011,根据寄存器功能可知此代表50Mhz推挽输出
|
||||||
|
GPIOB->ODR|=1<<5; //设置ODR第5位为1,输出高电平
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 GPIOB 的结构体如下所示:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
__IO uint32_t CRL;
|
||||||
|
__IO uint32_t CRH;
|
||||||
|
__IO uint32_t IDR;
|
||||||
|
__IO uint32_t ODR;
|
||||||
|
__IO uint32_t BSRR;
|
||||||
|
__IO uint32_t BRR;
|
||||||
|
__IO uint32_t LCKR;
|
||||||
|
} GPIO_TypeDef;
|
||||||
|
```
|
||||||
|
|
||||||
|
所以由以上提到的例子而言,C 语言可以从如下三方面进行与寄存器之间的控制:
|
||||||
|
|
||||||
|
1. 寄存器的地址可以使用<strong>指针变量</strong>来访问。
|
||||||
|
2. C 语言中的<strong>结构体可以用于表示寄存器映射</strong>。
|
||||||
|
3. C 语言中的<strong>位域可以用于表示寄存器中的位</strong>。
|
||||||
|
|
||||||
|
而且 C 语言中的内联汇编可以用于直接访问寄存器。在某些情况下,如果我们需要直接访问寄存器来完成复杂的硬件控制操作,则可以使用汇编语言指令来直接访问寄存器,从而实现复杂的硬件控制操作。常见的如,堆栈大小的设置等。
|
||||||
|
|
||||||
|
```asm
|
||||||
|
Stack_Size EQU 0x00000400
|
||||||
|
|
||||||
|
AREA STACK, NOINIT, READWRITE, ALIGN=3
|
||||||
|
Stack_Mem SPACE Stack_Size
|
||||||
|
__initial_sp
|
||||||
|
```
|
||||||
172
5.富有生命的嵌入式/5.3还玩裸机?上操作系统!.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# 还玩裸机?上操作系统!
|
||||||
|
|
||||||
|
> Author:肖扬
|
||||||
|
|
||||||
|
在 [New meaning of C](./5.2New%20meaning%20of%20C.md) 栏目中,我们已经从 stc51 单片机系列和 stm32 单片机系列进行了一定的裸机开发讲解,并且延伸到了通过相关的 datasheet 对刚接触到手的芯片进行开发(毕竟你总不能成为 stm32 开发工程师或者 cubemx 工程师对吧<del>【然鹅大多数急于求成的开发者往往忽视了这方面的能力,也许是为了更快入手竞赛】</del>)
|
||||||
|
|
||||||
|
而在此栏目中,我们将讲述相关操作系统在嵌入式上的应用,让你的嵌入式产品更加的智慧!(当然裸机并不一定就比带操作系统的嵌入式产品差,只是应用方向不同或者说有时候需要考虑产品的成本问题)
|
||||||
|
|
||||||
|
Ps:本栏目只例举几个目前开发工程中常见的操作系统的学习与开发,<strong>具体的移植过程可 web 或者自行探索-相信我,出色的移植能力是嵌入式开发者必不可少的。</strong>
|
||||||
|
|
||||||
|
## RTOS
|
||||||
|
|
||||||
|
MCU 本身就能做到一定的实时性,那为什么还是需要 RTOS(实时操作系统)呢,<strong>其实评判实时系统的关键并不是系统对外部事件的处理速度,而是处理事件的时间的可预见性和确定性。</strong>举个例子,Windows 操作系统在 CPU 没有其他任务时可以在很短的时间内对外部事件作出一定的响应,但是当某些后台任务在运行时,有时候响应速度会变得很慢甚至出现假死现象,这并不是因为 Windows 速度不够快或者效率不够高导致的,而是因为 Windows 对事件的响应不能提供准确性,所以其不能算作一个实时操作系统。<strong>并且在笔者看来,实时操作系统除了可以达到完成每次任务所需时间的一致性外,其相应的操作系统产品(例如我们本栏目将重点介绍的 FreeRTOS,这里可以简单提一下为啥要选 FreeRTOS,显而易见因为-Free)还具有可以简化整体架构、开发等工作的作用。</strong>
|
||||||
|
|
||||||
|
RTOS 中最重要的概念则是“任务”。
|
||||||
|
|
||||||
|
我们可以回想一下在 MCU 开发过程中,一般都是在 main 函数里做个 while(1)来完成大部分的处理,将一些相对来说对实时性要求高的函数(如 PID 控制器)扔到定时器中断当中,即应用程序是个无限的循环,是个单任务系统(前后台系统),while(1)作为后台,中断服务函数作为前台。这里采用了“正点原子”的一张图:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
而 RTOS 则是一个多任务系统,那么它这么做有什么好处呢?
|
||||||
|
|
||||||
|
2>1 嘛(乐),实际上在前后台系统中,你的每项 Task 要轮流排队等着上次 Task 执行结束后再进行自己的程序,大大影响了其系统的实时性要求;而 RTOS 中我们把整个 while(1)区分成了很多小任务,并且在表面上看起来这些任务运行起来像是同时进行,实际上是因为任务所需的时间较少导致它看起来像是并行,但这将会带来新的疑问,到底什么任务先执行呢?RTOS 就为此提供了任务的相关 API 接口,赋予任务相应的执行优先级属性,并通过任务调度器来控制任务的执行顺序。这里同样采用了“正点原子”的一张图:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
所以,<strong>其实可以这么说:RTOS 将整个流程变成了很多个 while(1)【每个任务都是个 while(1)】。</strong>
|
||||||
|
|
||||||
|
并且根据我上述所描述的内容,一个任务需要的属性大致如下(以启动函数为例进行介绍):
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define START_TASK_PRIO 1 //任务优先级
|
||||||
|
#define START_STK_SIZE 256 //任务堆栈大小
|
||||||
|
TaskHandle_t StartTask_Handler; //任务句柄
|
||||||
|
void start_task(void *pvParameters); //任务函数
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
systemInit();//硬件初始化
|
||||||
|
xTaskCreate(
|
||||||
|
TaskFunction_t start_task, //任务函数
|
||||||
|
const char * const "start_task", //任务名称
|
||||||
|
const uint16_t START_STK_SIZE, //任务堆栈大小
|
||||||
|
void * const NULL, //传递给任务函数的参数
|
||||||
|
UBaseType_t START_TASK_PRIO, //任务优先级
|
||||||
|
TaskHandle_t * const StartTask_Handler //任务句柄
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
至于有关于任务的运行态、挂起态、就绪态、阻塞态等我便不在此栏目进行讲解,希望读者能根据以上的引入去学习 FreeRTOS 的开发,可供参考的文档或者学习视频如下:
|
||||||
|
|
||||||
|
1、b 站正点原子官方 FreeRTOS 教学(在今年有做全面的更新,比之前讲的更为清晰,难得的优秀入门视频)
|
||||||
|
|
||||||
|
[https://www.bilibili.com/video/BV19g411p7UT/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7](https://www.bilibili.com/video/BV19g411p7UT/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7)
|
||||||
|
|
||||||
|
2、FreeRTOS 官网(官网往往是最适合学习的地方)[www.freertos.org](http://www.freertos.org)
|
||||||
|
|
||||||
|
## Linux(以 ROS 为例)
|
||||||
|
|
||||||
|
首先,如果你不了解 Linux 系统的话,我建议你去 [附加模块:Linux](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.Y%20%E9%99%84%E5%8A%A0%E6%A8%A1%E5%9D%97%EF%BC%9ALinux.md) 中进行一定的学习。
|
||||||
|
|
||||||
|
如果你不了解 Python,我建议你去以及 [3.6Python(灵巧的胶水)](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.6Python%EF%BC%88%E7%81%B5%E5%B7%A7%E7%9A%84%E8%83%B6%E6%B0%B4%EF%BC%89.md)中进行学习。
|
||||||
|
|
||||||
|
如果你已经对以上所涉及到的方面有了一定的了解,那么欢迎来到机器人开发者的殿堂-Robot Operating System!
|
||||||
|
|
||||||
|
由于硬件技术的飞速发展,针对于机器人软件设计的框架也面临着极大的挑战,而 ROS 的出现无异是所有机器人开发者的福音,因为如果按照以前的制作一个机器人流程来讲,也许你要经历以下步骤:硬件结构搭建、控制处理、相关算法构建等等,但是 ROS 的开源共享模式令其可以在其平台上巧妙利用别人的开源模型完成自己的机器人搭建,<strong>也就是说 Ros 的出现打破了原本各个开发者(或团队)闭门造车的开发现象,使得其可以共享优秀的机器人应用软件,换句话说就是提高了机器人研发的软件复用率。(毕竟哪个团队都不可能同时在建图、导航、视觉等机器人应用方面处于顶尖位置)</strong>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
由于 ROS 中完成度最高的是 Ubuntu,所以我们建议你以此展开学习,当然你也可以选择 macOS、Debian 等 OS。
|
||||||
|
|
||||||
|
但是 Linux 只是一个通用系统,并没有在机器人开发上提供相应的中间件,所以 ROS 提供了基于 TCP/UDP 的通信接口(机器人的工作当然需要通讯),在再此之上提供了相应的基础开发库供给至应用层。
|
||||||
|
|
||||||
|
此时应用层则有个很重要的概念-Master(管理者),其负责管理整个系统的正常运行。如果我们需要获得比较成熟的相关领域开源机器人包,按以往的操作必将是进行一次比较复杂的移植(你需要考虑各种因素:比如硬件支持、与其他移植包是否冲突等等)。<strong>但是在 ROS 系统中引入了功能包的概念,你可以在 ROS 社区中下载相关版本(与你的 ROS 版本相匹配)的机器人应用功能包,完成一次非常简单的移植过程(CV),你不需要关注其内部的具体运行机制,只需关注接口的定义与使用规则便可进行相应的二次开发(在 ROS 中你需要关注的是相关节点之间的关系,可以通过 rqt_graph 获取清晰的节点图),相当于给你做好了一个跟机器人开发有关的高集成度</strong><strong>SDK</strong><strong>平台。</strong>(当然如果你感兴趣的话可以做一定的了解,但这将牵扯到相关内容的庞大体系,比如如果你想了解自主导航是如何运行的,你首先需要了解 SLAM 算法的运行机制以及激光雷达或者相关深度摄像机的运用,然后你需要了解什么是深度信息什么是里程计信息,为什么可以表示机器人的位置信息,要如何进行一些相关的位置信息修正,然后 bulabula。<strong>以笔者自身的学习经历而言,学习相关的理论基础体系,将对你的二次开发有极大的帮助,而不会造成盲目使用接口的情况</strong>)
|
||||||
|
|
||||||
|
根据以上我讲述的相关内容可知:<strong>ROS 系统的优点在于,你能将社区里的有关机器人的开发模块集大成于一身,并且通过 ROS 与控制板通讯(此时类似于 STM32 的裸机主控便变成了控制板-用于接收 ROS 的调控完成相应电机、舵机的控制,或者完成一定的例如 oled 显示的简单任务),从而完成 ROS 系统内部开源算法对整个机器人的控制。</strong>
|
||||||
|
|
||||||
|
以下我简单介绍一下 ROS 的基础通讯方式:
|
||||||
|
|
||||||
|
在裸机开发中,我们的通讯(不论是蓝牙、WiFi 还是最基础的串口通讯)本质上都来自于串口中断,也就是说裸机开发引入了 ISR 来处理相应的数据接收。ROS 通讯中同样需要这样的函数来完成对目标数据的处理,而在 ROS 中我们称之为回调函数。
|
||||||
|
|
||||||
|
我们以话题通讯机制来做简要介绍,在此通讯中需要有两个节点,一个 Publisher(发布者)以及一个 Listener(订阅者),他们将发布和订阅同一个来完成相应的通讯-将发布和订阅的内容交给 ROS Master(整体流程类似于 WiFi 中的 mqtt 协议通信,将发布和订阅的内容交给公共服务器,形成一个转接的效果)。
|
||||||
|
|
||||||
|
所以通过这样的一个流程,具体的代码如下(以 C++ 为例):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
//Publisher
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
//首先我们要初始化节点
|
||||||
|
ros::init(argc, argv, "talker");
|
||||||
|
//其次,为了对这个类更好地进行处理,我们需要创建节点句柄
|
||||||
|
ros::NodeHandle n;
|
||||||
|
//创建一个Publisher=> pub,发布名为chat的topic,消息类型为std_msgs::String
|
||||||
|
ros::Publisher pub = n.advertise<std_msgs::String>("chat", 1000);
|
||||||
|
//设置循环的频率,对应着sleep延时
|
||||||
|
ros::Rate loop_rate(10);
|
||||||
|
|
||||||
|
while (ros::ok())
|
||||||
|
{
|
||||||
|
// 初始化std_msgs::String类型的消息
|
||||||
|
std_msgs::String msg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
对数据的处理
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 发布消息
|
||||||
|
ROS_INFO("%s", msg.data.c_str());
|
||||||
|
pub.publish(msg);
|
||||||
|
// 循环等待回调函数
|
||||||
|
ros::spinOnce();
|
||||||
|
// 按照循环频率延时
|
||||||
|
loop_rate.sleep();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
//Listener
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
//节点与节点句柄是必不可缺的
|
||||||
|
ros::init(argc, argv, "listener");
|
||||||
|
ros::NodeHandle n;
|
||||||
|
// 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatCallback
|
||||||
|
ros::Subscriber sub = n.subscribe("chat", 1000, chatCallback);
|
||||||
|
// 循环等待回调函数
|
||||||
|
ros::spin();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
而其中,在 Listener 中出现了回调函数,其功能类似于裸机中的中断控制-当接收到对应的订阅消息后,进行对应的数据、逻辑处理,例如如果我只是想把接收到的数据打印出来的话,我可以这么写回调函数 chatCallback:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void chatterCallback(const std_msgs::String::ConstPtr& msg)
|
||||||
|
{
|
||||||
|
ROS_INFO("I heard: [%s]", msg->data.c_str());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
至于 ROS 其他的内容便不在这里阐述,ROS 是机器人开发者的福音,此模块是希望有更多地人能注意到这样的良心开源平台,并督促各位进行相关的知识学习,可供参考的文档或者学习视频如下:
|
||||||
|
|
||||||
|
1、b 站古月居 ROS21 讲(讲的比较浅,但是可以作为入门学习视频,了解整个框架,感兴趣地可以看胡老师的 ROS2 系列视频,毕竟 ROS1 近期已经停更了,要保持不断学习的姿态)
|
||||||
|
|
||||||
|
[https://www.bilibili.com/video/BV1zt411G7Vn/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7](https://www.bilibili.com/video/BV1zt411G7Vn/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7)
|
||||||
|
|
||||||
|
<strong>提一嘴:很多人学 ROS 就学一个开头-比如就学了古月居的 21 讲,就认为自己已经了解到了 ROS 的大部分内容了</strong><strong>(不会有人现在还是纯看视频学习吧)</strong><strong>,实际上这是非常错误的想法。当你学完了视频的内容后,你甚至可能不会移植 wiki 上的功能包(x_x),甚至不知道如何去开发一个真实的机器人(因为此 21 讲只是理论上的讲解,去做一个虚拟机器人在 gazebo 上运行)。ROS 的学习上需要我们花大量的心思去学会接触新的东西,你们并不能只局限于我提供的推荐学习资料,因为相应的功能包不是一成不变的,而且也不是只有那么几个功能包,当你感受了 ROS 的自主建图、自主导航、机械臂控制、机器学习开发等等等等等等后,你才会发现 ROS 的世界是如此美妙!</strong>
|
||||||
|
|
||||||
|
2、b 站赵虚左 ROS 课程(讲得细致多了,需要耐心看下去,要是我入门 ROS 的时候有这个视频就好了)
|
||||||
|
|
||||||
|
[https://www.bilibili.com/video/BV1Ci4y1L7ZZ/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7](https://www.bilibili.com/video/BV1Ci4y1L7ZZ/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7)
|
||||||
|
|
||||||
|
3、古月居的《ROS 机器人开发实践》(根据国外的《ROS By Example》改编,但是更贴近于入门开发,会有相关功能包更细致的解析)
|
||||||
|
|
||||||
|
4、[http://wiki.ros.org/cn](http://wiki.ros.org/cn)(不会用 ROSwiki 的 ROS 玩家不是好 ROS 玩家)
|
||||||
|
|
||||||
|
如果未来有能力的话,希望我们能反哺 ROS 社区,促进开源社区的发展。
|
||||||
|
|
||||||
|
## 其他常见嵌入式操作系统(入门仅作了解)
|
||||||
|
|
||||||
|
相对于通用计算机操作系统而言,嵌入式操作系统除有任务调度、同步机制、内存管理、中断处理、文件处理等基本功能外更有其强大的实时性、强稳定性、硬件适应性等等。与通用计算机不同,嵌入式操作系统行业是一个百家争鸣的环境,没有一款产品能够占据绝对统治地位,但是也有很多应用面较广、有突出特色的嵌入式操作系统享有较高的知名度。
|
||||||
|
|
||||||
|
1、微软的嵌入式 Windows 操作系统(从个人计算机到嵌入式系统的更变)
|
||||||
|
|
||||||
|
此嵌入式操作系统的初衷就是“使当今的个人计算机复杂的软件环境扩展到嵌入式世界”,也就是说希望能在嵌入式产品上用到 windows 系统的桌面软件。主流上 EOS 分为两个:Windows Embedded Compact(WEC)系列和 Windows Embedded(WE)系列。常见用于手持设备、机顶盒、数码娱乐设备等等。
|
||||||
|
|
||||||
|
2、VxWorks(集多种处理器大成的操作系统)
|
||||||
|
|
||||||
|
VxWorks 是硬实时操作系统,比 FreeRTOS 这类软实时操作系统有更强的实时性,更加专注于人的交互性、可靠性,用于军事、航空、控制等领域。并且 VxWorks 支持 PowerPC、CPU32、x86 等多种嵌入式处理器体系,所以有较好的可裁剪性、兼容性。
|
||||||
BIN
5.富有生命的嵌入式/static/CZ3cbiEhsoWDgJxhwXIcpUkAnMg.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
5.富有生命的嵌入式/static/HTFUbsQCNouQVzx0QYiciQWOnZf.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
5.富有生命的嵌入式/static/LJ1SbFfv6oUIgtx8CstcbWTNnRg.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
5.富有生命的嵌入式/static/MyDMbeCKLowC1Mx7Q6Ec9BLPn4g.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
5.富有生命的嵌入式/static/boxcnFySF1Cd02I052V0a9glH1c.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
5.富有生命的嵌入式/static/boxcnRy7E27xggqNshXX3cu4J5Q.png
Normal file
|
After Width: | Height: | Size: 682 KiB |
BIN
5.富有生命的嵌入式/static/boxcntQgR61yRboDpyb1bpI10Xp.png
Normal file
|
After Width: | Height: | Size: 58 KiB |