首页 > 应用与设计 > 电器 > Service Robots > 服务型机器人 > TI机器人系统学习套件(TI-RSLK) >

服务型机器人

最新课程

热门课程

TI-RSLK 模块 4 - 讲座视频 - C 语言编程

大家好,我是 Jon Valvano。 在本次讲座中, 我们将讨论 C 语言编程。 我建议您找一本 比较好用的 C 语言参考书, 以便在本课程中使用。 本讲座的目标是 简要介绍一下 一些您需要在 C 语言编程过程中 时刻注意的 问题和概念。 在实验中,您将通过 实施该双曲线函数 完成转换, 您采用该输入, 将其拟合到该曲线, 然后得到对应的输出。 在 C 语言编程实验中, 您需要解决的另一个问题是, 思考机器人 在跑道上的位置。 所以,当机器人来到 迷宫中的这个点时, 您将会询问, 我能直行吗? 能右转吗? 或者能左转吗? 您将会通过 一系列条件语句 解决这个问题。 好,让我们开始吧。 如果您知道 流程图是什么, 我们建议您使用这种图, 因为这种方式能够让您 以图表的形式描述 软件算法、方法、 一组步骤, 或者是软件必须 遵循的执行序列。 我们具有起始点 和结束点。 具有可以选择向左 还是向右的条件。 我们可以进行输入、输出。 可以执行计算。 我们甚至可以让一个函数 调用另一个函数。 在 C 语言中, 这称为结构化语言, 因为它的构建形式 包括以下四种构建块。 如果我们有一个序列, 先执行 A,然后再执行 B, 这种情况便称为序列。 我们可能会有 一个测试条件, 如果测试结果为 true, 则从这边走, 如果测试结果为 false, 则从这边走。 此外,还有两种循环。 我们可以先执行测试, 如果测试结果为 true, 我们将循环执行 while 循环的主体, 直至测试结果成为 false。 或者,我们将 至少执行主体一次, 然后进行测试。 同样,如果 测试结果为 true, 我们将循环 反复执行该主体, 直至测试结果成为 false。 再强调一次,如果您知道 流程图是什么, 而且您绘制的软件 可以归类为算法, 我们建议您, 先绘制流程图, 再写代码。 逻辑运算 在嵌入式系统中 非常重要。 我们将会在两种情况下 使用“与”运算。 我们可以将其作为 一个掩码来选择位。 如果使用 1 来进行掩码运算, 使用 1 进行“与”运算, 我们将选择相应的位。 如果使用 0 进行“与”运算, 则会清除相应的位。 这个运算就在这里, 您可以看到, 它会选择第 1 和第 0 位, 因为这两个位 是恒定的。 我们可以使用 “或”运算来设置一个位。 如果我们用 1 对其进行 “或”运算,便会设置该位。 我们还可以使用 “或”运算来取并集, 我们可以取两个不同的值, 在二者之间进行变换, 使用“或”运算 将两个值的结果 合并到一个变量中。 然后是“异或”运算, 我们将用其进行切换。 切换即翻转,当我们使用 1 进行“异或”运算时, 如果原来是 0, 则会变成 1。 如果原来是 1, 则会变成 0, 这是幅度与时间的关系。 使用 0 进行“异或”运算时 不会带来变化。 这种运算以按位方式完成。 所以,对于这里的 这个“或”运算, x 是一个 32 位的数字-- 第 31 位,第 29 位-- 我的意思是从第 30 位, 第 29 位,一直到最后的 第 0 位。 Y 也是一个 32 位的数字-- 第 31 位、30 位、 29 位,一直到第 0 位。 当我执行所有这些 逻辑运算时, 系统会以按位方式进行。 因此,举例来说, 系统会将 x 的第 29 位的值 与 Y 的 第 29 位进行“或”运算, 并将结果存储在 Z 的第 29 位中。 这个过程适用于 所有的 32 个位。 以切换为例-- 我们将在整个课程中 了解各种端口-- 但这是第一个端口。 这是微控制器上的 一个引脚。 它是一个输出引脚。 如果我们想要 生成这个波形, 我们可以执行这行 C 代码, 这行代码只会对第 3 位进行切换, 其他 31 位会 保持不变。 我们可以在这些时间 执行该行代码。 同样,这也是 振幅与时间之间的关系。 如果我们在这个时间 执行这行 C 代码, 我们将会生成 这段波形。 所以,我们将会 在这节课中使用切换。 我们会在多种 情况下使用移位。 如果我们想做除法, 我们可以执行右移。 那么,让我们除以 2。 如果我们想做乘法, 我们可以执行左移。 我们可以针对 2 的 任何次方进行运算。 我们可以 一次移动多位。 但是,具体的移动方式 应由您自己决定, 因为您需要了解 运算对象是否有符号, 因为如果没有符号, 您将需要移入 0。 如果有符号, 则需要保留该位。 所以符号位-- 所以,进行移位的负数 在除以 2 的次方后, 仍然为负数。 要做到这一点的方式是, 选择相应的类型。 在 C99 中,以 U 开头的 类型为无符号类型。 不是以 U 开头, 而是以 I 开头的类型 则是有符号类型。 当您使用左移 执行乘法运算时, 这两种类型都没有问题。 再强调一下,移位运算用于 做除法或乘法。 它通常是先进行各自的移位运算, 然后再取它们的 并集。 同样,您需要 记录并确保 自己清楚地了解 您的数字是有符号的 还是无符号的。 算术运算也同样如此。 所以您同样需要 了解运算对象是否 有符号。 但是我们还会遇到 另外一个问题,那就是, 如果我取两个 32 位的 数字,将其相加, 我实际上会得到 33 位。 如果我尝试将这个 33 位的数字压缩到 32 位的变量中, 数据将会被破坏。 系统将会给出一个 严重错误的答案, 这种现象称为溢出。 乘法运算时的情况会更糟糕。 取两个 32 位的 数字,将其相乘, 我实际上会得到 64 位。 同样,如果您尝试将这个 64 位的结果压缩到 32 位的变量中, 系统可能会给出错误的答案。 因此,加法、减法 和乘法都有可能 发生溢出。 您将可以通过两种 方式来处理这种 溢出。 一种方法是 限制输入值。 例如,如果您知道 输入是一个 12 位的数字, 如果您知道它不是 32 位的数字, 而是 12 位的数字, 那么它的范围为 0 到 4,095。 然后,您使用 另外一个数值已知的 数字与该数字进行运算。 举个简单的 例子,1,000, 1,000 是一个 10 位的数字。 如果您乘以这个数字, 您知道可能的最大值为 4 百万 不可能再比它更大了。 我们知道 4 百万 是可以放在一个 32 位的变量中的。 所以,您知道, 在这种情况下, 如果我对输入进行限制, 运算就不可能出现溢出。 我们可以看到, 如果您取一个 12 位的数字, 将其与一个 10 位的 数字相乘, 您会得到 22 位, 这能够装入 32 位的结果中。 所以,您用于处理 溢出的第一种方式是, 了解您所有输入 变量的取值范围。 然后,通盘考虑 所有的计算, 确保没有 超出 32 位的 中间结果。 另外一种处理方式是, 升级、执行、检查, 然后降级。 我们将在下面进行说明。 如果您有两个 8 位的数字, 您想要将 这两个数字相加, 那么您将会得到一个 9 位的数字。 这时便没有什么转圜的余地。 但是,如果您将 8 位的数字 升级到 32 位-- 我已经进行了升级。 这是一个 32 位的数字。 我在 32 位 模式下执行运算, 然后检查 结果是否超出 8 位的变量。 如果超出,我会 切掉相应的高位。 然后降级至 8 位。 这并不是正确的答案, 但要比允许 溢出的情况好。 在处理算术运算时, 您的工作是 正确处理您的 数字是无符号数字, 还是有符号数字。 同样,在 C99 中, 您将通过选择相应的 类型来做到这一点。 无论您使用的是 8 位类型、16 位类型, 还是 32 位类型。 对于除法, 您不会想要除以 0。 关于除法的 另一个问题是 掉出,即信息丢失。 这种情况很常见。 如果我任意取一个数字, 将其除以 1,024, 这是一个整数除法。 它从本质上是一个右移运算。 我们将会 丢失 10 位, 就是这样。 这个运算将会 取一个 32 位的数字, 并切掉它后面的 10 位。 所以每次在做除法 或者说右移运算时, 我们都会遇到掉出的问题, 也就是数据丢失。 当我们执行计算, 以了解数字 在计算中的 运行方式时, 我们将会再次 清晰地认识到这一点。 所以这两个问题是加法和 乘法或左移运算中的溢出问题, 以及右移运算 或除法中的掉出问题。 条件语句, 希望您注意这个元素, 因为我们可以看到, “与”运算是针对 总共 32 位上的值 以按位方式执行的 逻辑运算。 而双与,还有双或, 则是布尔运算。 布尔值是 true 或 false 中的一个。 0 代表 false, 非 0 则代表 true。 双与、双或和非 是布尔运算。 也就是说, 它们对其他 true、false 进行 true、false 运算, 得到值为 true 或 false 的结果。 关系运算是 取两个数字, 一个数字 n,一个数字 m, 结果是一个布尔值。 当我执行关系运算时, 比如大于、小于、 大于等于、等于 或不等于,我将会 得到一个布尔输出。 而且我可以将这些 运算置于类似这样的 决策中,如果 G1 小于 G2, 且 G3 不等于 G4, 那么我将会 执行这一步, 否则,则会执行这一步。 我可以在 if 语句中 使用“与”运算。 如果设置了第 7 位, 则结果为 true。 如果清除了第 7 位, 则结果为 false。 要再次提醒您的是, 您必须清楚, 自己谈论的是 以按位方式 进行的数字运算, 还是以每次一个的方式 进行的布尔运算。 上述情况同样适用于循环结构。 while 循环和 do while 循环 都是采用布尔值。 因此,这样会计算得到 true 或 false。 我们将在这里放置 true。 因此,在本例中, while 循环会先执行测试。 如果 G2 大于 G1, 则会一遍又一遍地 执行该主体, 直至 G2 小于 或等于 G1, 然后停止执行主体。 do while 循环 会执行主体至少 一次,然后进行测试。 同样,如果 G2 大于 G1, 则会执行主体一次, 然后同样一遍又一遍地 执行,直至 G2 小于 或等于 G1。 有时,我们会一遍 又一遍地执行某个操作, 更具体一点,我们可以 执行某个操作 10 次。 但是如果您注意一下 for 循环,它非常 简单,本质上也是 一个 while 循环。 我们将会取用 这部分代码, 并使其执行一次。 我们将会取用 这部分代码, 这部分将用作测试。 我们将会执行主体。 然后这部分代码会在 主体执行之后, 一遍又一遍地执行。 这种情况下 会执行主体 10 次。 您可以使用向上计数或向下计数。 这都没有关系。 while 循环 允许您在条件 仍然为 true 的情况下, 一遍又一遍地执行 某个操作。 上次我们看到, 函数在这个抽象中 非常重要。 允许您将 高层次的功能 与低层次的 工作方式 分离开来。 我们会在 头文件中 将它的功能作为 公有函数的原型。 我们将会实施 该函数,并在代码 文件中展示 所有琐碎细节, 同时尽量隐藏这些细节。 实施文件或代码文件中 包含公有函数的 所有细节和原型。 也就是说, 使用该模块所需要 了解的内容都将 被放在头文件中。 这种抽象 使得我们能够 实现非常高的复杂性。 函数的第三个方面是 如何进行调用。 在本课程中, 您将会看到 许多主程序,用来 说明如何使用函数。 我们可以在这里看到, 我们通过调用函数的名称 来调用函数。 而且我们可以 传入其参数。 这个是 1, 我们将其传入该函数。 数字 1 被传递给 x。 x 的值被传递给 这个全局变量-- 好吧, 是静态全局变量 m。 然后,我们也可以 从函数返回参数, 并将其存储到一个位置。 同样,这里的 返回操作 将会传回一个参数。 这是一个 随机数生成器。 这不是一个非常好的 随机数生成器, 但是它会返回 0 到 255 之间的一个数字。 因此,函数 在 C 语言编程中 非常重要。 建议您看看 示例文件是 如何设置的,以了解 您该在本课程中如何进行操作。 变量,这张幻灯片, 再加上后面的两张, 可能是本讲座中 最重要的幻灯片了, 因为,它们介绍了 可让您成为一个优秀 程序员的各种变量。 我们可以讨论 重要变量的名称, 而且一个变量的名称应该 阐明这个变量是什么。 但是今天我想要 讨论以下两点。 我想要讨论的是范围, 也就是谁可以访问这个变量。 分配则是关于 这个变量何时存在。 所以,当我们限制 范围时,我们称其为 私有变量,不限制范围时, 则为公有变量。 分配则分为临时 分配和永久性分配。 也就是说, 变量会永久存在吗? 所以,全局变量是一个 具有公有范围的变量, 这意味着任何函数都可以使用它。 如果我定义 这样一个变量, 并将其置于 软件中的 任意位置, 其他模块便可以 将其用作外部变量, 并拥有对这个变量的读写权限。 这是一件很糟糕的事情。 它具有永久性分配, 也就是说,它会永远 存在于存储器内的 某个地址上。 有时,我们确实需要 永久性分配,但是我们 可以不使用全局变量。 这时,我们会创建 一个静态变量。 这里便是一个 静态变量的例子。 它具有永久性分配, 但是其范围是私有的-- 针对该文件的私有范围。 因此,只有该文件中的 函数才能访问 m。 其他函数无法将其用作 外部变量并获取该变量。 这种范围限制 将会降低复杂性。 局部变量具有私有 范围和动态分配, 其中动态分配 意味着它临时存储在 寄存器中或堆栈上。 这是一件好事, 因为这样您便可以 重复使用该资源。 这种变量的范围 局限于相应的函数。 例如,这里便有 一个局部变量。 这里是另外 一个局部变量。 它们具有相同的名称, 但是却是不同的 变量。 所以,这个局部变量的 范围是这个函数。 而这个局部变量的 范围是这个函数。 事实证明,我们还需要 考虑局部参数。 这个参数 x 的 访问范围仅限于 这个函数。 所以这是一个局部变量。 因此,还有另外 一种静态变量, 这种静态变量 也具有永久性分配 和私有范围。 但是,如果我将 该静态变量放在这里, 那么它的范围 将仅限于该函数内。 这是一个仅发生一次的 静态变量,它等于 0, 因此,它在存储器的 某个位置上具有 永久性分配,而且会在 启动时初始化为 0。 之后每次 调用该函数, 它都会递增。 所以,您可以看到 它的功能是什么。 它将会对调用 随机数生成器的 次数进行计数, 如果这是 您想要的。 所以,在这里, 我们的工作是 了解范围和分配。 我们想要做的是, 尽量减小范围。 我们对范围的 限制越严格, 我们的系统就越简单。 因此,我们必须了解, 谁实际上需要知道, 或者说访问该变量。 如果我能减小它的范围, 我的系统就会变得更简单。 这个原则同样 适用于分配。 如果我能够动态分配变量, 方法包括将变量置于堆栈上, 在寄存器中使用变量, 或者在 C 代码中将其设置为局部变量, 那么我就可以重复使用该资源。 有时,我真的需要永久性地 记录某些信息, 那么,我将会为其提供 永久性分配。 但是,正如在上一张幻灯片中 所讲的,我希望将其作为静态变量, 以便仅允许相应 文件访问该变量。 跟其他问题 一样,我们也在 几乎每张相关的幻灯片内 反复提及以下问题, 即处理对象是有符号的 还是无符号的。 再说明一次,无符号 C99 变量以 U 开头, 有符号变量 以 Int 开头。 请注意并明确自己 谈论的是 8 位、16 位, 还是 32 位类型。 我们在前面提到过, 其中一种处理方式是 进行升级,因为 您无法将一个有符号 数字与一个 无符号数字相加。 您无法将一个有符号 数字与一个无符号数字 进行比较,但是您 可能又想要这样做。 您将要做的是, 将它们升级到 同一类型,在这一 新类型下执行运算, 然后将其降级 或保持不变。 这向您展示了一种可以 在不丢失信息的情况下 进行升级的方式。 所以,一个 8 位数字可以 成为一个 16 位数字, 一个 8 位数字可以成为一个 16 位或 32 位数字, 无符号 16 位可以成为 无符号和有符号的 32 位, 有符号的 16 位可以 成为有符号的 32 位。 在讨论变量的同时, 让我们来讨论一下 I/O 端口。 I/O 端口 1,输出。 我们将会在后面 详细介绍各个端口, 这里只是想说明 该端口存在于存储器中, 所以其行为方式与变量类似。 当您从德州仪器 获取该端口时, 它存在于一个大家 都知道的地址上。 由于它具有 永久性的地址, 而且大家到知道 具体的位置, 因此,它在形式上和规则上 是一个全局变量。 它支持公共访问, 而且具有永久性分配。 我说过这样很糟糕。 任何软件都可以访问该端口。 但是,为了 降低复杂性, 我们将会从另外一种角度 来看待端口寄存器。 不是关注谁可以访问, 而是关注谁会访问。 如果我们限制谁会访问 我们的端口寄存器, 这将会大大降低 软件的复杂性。 因此,从实践 角度出发, 我们将把 I/O 寄存器视为 类似静态变量的 私有永久性元素。 所以,您对程序中 各元素的范围的 限制越严格,您的 软件就越简单。 常量,我们可以将它们放在这里, 并使用定义语句。 这里又用到了这个 非曲线函数, 我们取输入值, 并计算距离。 该曲线的形状 将通过这两个 数字进行编码。 如果我们有许多常量, 我们可以将它们放在 ROM 中, 这一点可通过 Const 实现。 有时,我们可能想要 使用词语,而不是数字。 这时,您将会 使用枚举类型。 所以,如果机器人-- 这是我的机器人, 将沿这条路移动。 这是墙壁。 如果我离右侧 墙壁太近了, 我可能想要说, 我离右侧墙壁太近了, 而不是简单地说 X 等于 2, 虽然这也是在表示离右侧墙壁 太近,我能做的是 使用枚举类型。 这是一个标头, 它实际上 并不是变量, 而只是一个结构。 如果我想要一个变量, 我将会这样做。 比如说 scenario_t, 这是一个类型-- 下划线 t 表示这是一个类型。 然后,我将会创建一个变量。 如果我的情况是 离右侧墙壁太近, 我可以将 me 设置为 等于 right too close。 总的来说, 这两者其实代表的是 同一件事情。 只是这可以让您的 代码更易于阅读。 这同样也是一个常量, 我们都知道它的值 将会是 2。 但是,这让我的软件 更加易于阅读。 这便是创建常量的 三种不同的方式。 我们可以讨论您的 行事方式。 如果您有一个 A, 一个 B。执行 A,然后 执行 B,这句话 是一个序列。如果 A 为 true, 那么执行 B,这是条件语句。 执行 A,直至 B,这可能 是一个 while 循环。 我们会看到一些事件, 看到很多中断。 所以,我们可以创建一些触发器。 当您触摸按钮时, 我们可能会 执行某个操作, 比如停止电机转动。 如果撞到墙壁, 则停止电机转动。 这便是一个中断。 我们将会看到的 另外一种中断是, 每秒钟执行 某个操作 100 次。 比如,我们可能会每秒 对传感器采样 100 次。 好的,总结一下, 我们了解了 “与”运算相关的 一些基本问题, 并且我还提醒您注意 处理对象是有符号的 还是无符号的, 是 8 位、16 位, 还是 32 位。 对于变量, 请确保您了解 其范围,也就是谁可以访问, 并且如果可以, 尽可能地将其作为 静态变量或局部变量, 以限制其范围。 然后是关注分配, 也就是变量存在于哪里。 希望您喜欢本课程, 我们下次再见。 501

大家好,我是 Jon Valvano。

在本次讲座中, 我们将讨论 C 语言编程。

我建议您找一本

比较好用的 C 语言参考书,

以便在本课程中使用。

本讲座的目标是 简要介绍一下

一些您需要在 C 语言编程过程中

时刻注意的 问题和概念。

在实验中,您将通过 实施该双曲线函数

完成转换, 您采用该输入,

将其拟合到该曲线, 然后得到对应的输出。

在 C 语言编程实验中, 您需要解决的另一个问题是,

思考机器人 在跑道上的位置。

所以,当机器人来到 迷宫中的这个点时,

您将会询问, 我能直行吗?

能右转吗?

或者能左转吗?

您将会通过 一系列条件语句

解决这个问题。

好,让我们开始吧。

如果您知道 流程图是什么,

我们建议您使用这种图, 因为这种方式能够让您

以图表的形式描述 软件算法、方法、

一组步骤, 或者是软件必须

遵循的执行序列。

我们具有起始点 和结束点。

具有可以选择向左 还是向右的条件。

我们可以进行输入、输出。

可以执行计算。

我们甚至可以让一个函数 调用另一个函数。

在 C 语言中, 这称为结构化语言,

因为它的构建形式 包括以下四种构建块。

如果我们有一个序列, 先执行 A,然后再执行 B,

这种情况便称为序列。

我们可能会有 一个测试条件,

如果测试结果为 true, 则从这边走,

如果测试结果为 false, 则从这边走。

此外,还有两种循环。

我们可以先执行测试, 如果测试结果为 true,

我们将循环执行 while 循环的主体,

直至测试结果成为 false。

或者,我们将 至少执行主体一次,

然后进行测试。

同样,如果 测试结果为 true,

我们将循环 反复执行该主体,

直至测试结果成为 false。

再强调一次,如果您知道 流程图是什么,

而且您绘制的软件 可以归类为算法,

我们建议您, 先绘制流程图,

再写代码。

逻辑运算 在嵌入式系统中

非常重要。

我们将会在两种情况下 使用“与”运算。

我们可以将其作为 一个掩码来选择位。

如果使用 1 来进行掩码运算, 使用 1 进行“与”运算,

我们将选择相应的位。

如果使用 0 进行“与”运算, 则会清除相应的位。

这个运算就在这里, 您可以看到,

它会选择第 1 和第 0 位, 因为这两个位

是恒定的。

我们可以使用 “或”运算来设置一个位。

如果我们用 1 对其进行 “或”运算,便会设置该位。

我们还可以使用 “或”运算来取并集,

我们可以取两个不同的值, 在二者之间进行变换,

使用“或”运算 将两个值的结果

合并到一个变量中。

然后是“异或”运算, 我们将用其进行切换。

切换即翻转,当我们使用 1 进行“异或”运算时,

如果原来是 0, 则会变成 1。

如果原来是 1, 则会变成 0,

这是幅度与时间的关系。

使用 0 进行“异或”运算时 不会带来变化。

这种运算以按位方式完成。

所以,对于这里的 这个“或”运算,

x 是一个 32 位的数字--

第 31 位,第 29 位--

我的意思是从第 30 位, 第 29 位,一直到最后的

第 0 位。

Y 也是一个 32 位的数字--

第 31 位、30 位、 29 位,一直到第 0 位。

当我执行所有这些

逻辑运算时, 系统会以按位方式进行。

因此,举例来说, 系统会将 x 的第 29 位的值

与 Y 的

第 29 位进行“或”运算, 并将结果存储在 Z 的第 29 位中。

这个过程适用于 所有的 32 个位。

以切换为例--

我们将在整个课程中 了解各种端口--

但这是第一个端口。

这是微控制器上的 一个引脚。

它是一个输出引脚。

如果我们想要 生成这个波形,

我们可以执行这行 C 代码, 这行代码只会对第 3 位进行切换,

其他 31 位会 保持不变。

我们可以在这些时间 执行该行代码。

同样,这也是 振幅与时间之间的关系。

如果我们在这个时间 执行这行 C 代码,

我们将会生成 这段波形。

所以,我们将会 在这节课中使用切换。

我们会在多种 情况下使用移位。

如果我们想做除法, 我们可以执行右移。

那么,让我们除以 2。

如果我们想做乘法, 我们可以执行左移。

我们可以针对 2 的 任何次方进行运算。

我们可以 一次移动多位。

但是,具体的移动方式 应由您自己决定,

因为您需要了解 运算对象是否有符号,

因为如果没有符号, 您将需要移入 0。

如果有符号, 则需要保留该位。

所以符号位--

所以,进行移位的负数 在除以 2 的次方后,

仍然为负数。

要做到这一点的方式是, 选择相应的类型。

在 C99 中,以 U 开头的 类型为无符号类型。

不是以 U 开头, 而是以 I 开头的类型

则是有符号类型。

当您使用左移 执行乘法运算时,

这两种类型都没有问题。

再强调一下,移位运算用于 做除法或乘法。

它通常是先进行各自的移位运算, 然后再取它们的

并集。

同样,您需要 记录并确保

自己清楚地了解 您的数字是有符号的

还是无符号的。

算术运算也同样如此。

所以您同样需要 了解运算对象是否

有符号。

但是我们还会遇到 另外一个问题,那就是,

如果我取两个 32 位的 数字,将其相加,

我实际上会得到 33 位。

如果我尝试将这个 33 位的数字压缩到

32 位的变量中, 数据将会被破坏。

系统将会给出一个 严重错误的答案,

这种现象称为溢出。

乘法运算时的情况会更糟糕。

取两个 32 位的 数字,将其相乘,

我实际上会得到 64 位。

同样,如果您尝试将这个 64 位的结果压缩到

32 位的变量中, 系统可能会给出错误的答案。

因此,加法、减法 和乘法都有可能

发生溢出。

您将可以通过两种 方式来处理这种

溢出。

一种方法是 限制输入值。

例如,如果您知道 输入是一个 12 位的数字,

如果您知道它不是 32 位的数字, 而是 12 位的数字,

那么它的范围为 0 到 4,095。

然后,您使用 另外一个数值已知的

数字与该数字进行运算。

举个简单的 例子,1,000,

1,000 是一个 10 位的数字。

如果您乘以这个数字, 您知道可能的最大值为

4 百万

不可能再比它更大了。

我们知道 4 百万 是可以放在一个

32 位的变量中的。

所以,您知道, 在这种情况下,

如果我对输入进行限制, 运算就不可能出现溢出。

我们可以看到, 如果您取一个 12 位的数字,

将其与一个 10 位的 数字相乘,

您会得到 22 位, 这能够装入 32 位的结果中。

所以,您用于处理 溢出的第一种方式是,

了解您所有输入 变量的取值范围。

然后,通盘考虑 所有的计算,

确保没有 超出 32 位的

中间结果。

另外一种处理方式是, 升级、执行、检查,

然后降级。

我们将在下面进行说明。

如果您有两个 8 位的数字,

您想要将 这两个数字相加,

那么您将会得到一个 9 位的数字。

这时便没有什么转圜的余地。

但是,如果您将 8 位的数字 升级到 32 位--

我已经进行了升级。

这是一个 32 位的数字。

我在 32 位 模式下执行运算,

然后检查 结果是否超出

8 位的变量。

如果超出,我会 切掉相应的高位。

然后降级至 8 位。

这并不是正确的答案,

但要比允许 溢出的情况好。

在处理算术运算时, 您的工作是

正确处理您的 数字是无符号数字,

还是有符号数字。

同样,在 C99 中, 您将通过选择相应的

类型来做到这一点。

无论您使用的是 8 位类型、16 位类型,

还是 32 位类型。

对于除法, 您不会想要除以 0。

关于除法的 另一个问题是

掉出,即信息丢失。

这种情况很常见。

如果我任意取一个数字, 将其除以 1,024,

这是一个整数除法。

它从本质上是一个右移运算。

我们将会 丢失 10 位,

就是这样。

这个运算将会 取一个 32 位的数字,

并切掉它后面的 10 位。

所以每次在做除法 或者说右移运算时,

我们都会遇到掉出的问题, 也就是数据丢失。

当我们执行计算, 以了解数字

在计算中的 运行方式时,

我们将会再次 清晰地认识到这一点。

所以这两个问题是加法和 乘法或左移运算中的溢出问题,

以及右移运算 或除法中的掉出问题。

条件语句, 希望您注意这个元素,

因为我们可以看到, “与”运算是针对

总共 32 位上的值 以按位方式执行的

逻辑运算。

而双与,还有双或,

则是布尔运算。

布尔值是 true 或 false 中的一个。

0 代表 false, 非 0 则代表 true。

双与、双或和非

是布尔运算。

也就是说, 它们对其他 true、false

进行 true、false 运算, 得到值为 true 或 false 的结果。

关系运算是 取两个数字,

一个数字 n,一个数字 m, 结果是一个布尔值。

当我执行关系运算时,

比如大于、小于、 大于等于、等于

或不等于,我将会 得到一个布尔输出。

而且我可以将这些 运算置于类似这样的

决策中,如果 G1 小于 G2,

且 G3 不等于 G4, 那么我将会

执行这一步, 否则,则会执行这一步。

我可以在 if 语句中

使用“与”运算。

如果设置了第 7 位, 则结果为 true。

如果清除了第 7 位, 则结果为 false。

要再次提醒您的是, 您必须清楚,

自己谈论的是 以按位方式

进行的数字运算, 还是以每次一个的方式

进行的布尔运算。

上述情况同样适用于循环结构。

while 循环和 do while 循环 都是采用布尔值。

因此,这样会计算得到 true 或 false。

我们将在这里放置 true。

因此,在本例中, while 循环会先执行测试。

如果 G2 大于 G1, 则会一遍又一遍地

执行该主体,

直至 G2 小于 或等于 G1,

然后停止执行主体。

do while 循环 会执行主体至少

一次,然后进行测试。

同样,如果 G2 大于 G1,

则会执行主体一次, 然后同样一遍又一遍地

执行,直至 G2 小于 或等于 G1。

有时,我们会一遍 又一遍地执行某个操作,

更具体一点,我们可以 执行某个操作 10 次。

但是如果您注意一下 for 循环,它非常

简单,本质上也是 一个 while 循环。

我们将会取用 这部分代码,

并使其执行一次。

我们将会取用 这部分代码,

这部分将用作测试。

我们将会执行主体。

然后这部分代码会在 主体执行之后,

一遍又一遍地执行。

这种情况下 会执行主体 10 次。

您可以使用向上计数或向下计数。

这都没有关系。

while 循环 允许您在条件

仍然为 true 的情况下, 一遍又一遍地执行

某个操作。

上次我们看到, 函数在这个抽象中

非常重要。

允许您将 高层次的功能

与低层次的 工作方式

分离开来。

我们会在 头文件中

将它的功能作为 公有函数的原型。

我们将会实施 该函数,并在代码

文件中展示 所有琐碎细节,

同时尽量隐藏这些细节。

实施文件或代码文件中

包含公有函数的

所有细节和原型。

也就是说, 使用该模块所需要

了解的内容都将 被放在头文件中。

这种抽象 使得我们能够

实现非常高的复杂性。

函数的第三个方面是 如何进行调用。

在本课程中, 您将会看到

许多主程序,用来 说明如何使用函数。

我们可以在这里看到, 我们通过调用函数的名称

来调用函数。

而且我们可以 传入其参数。

这个是 1, 我们将其传入该函数。

数字 1 被传递给 x。

x 的值被传递给 这个全局变量-- 好吧,

是静态全局变量 m。

然后,我们也可以 从函数返回参数,

并将其存储到一个位置。

同样,这里的 返回操作

将会传回一个参数。

这是一个 随机数生成器。

这不是一个非常好的 随机数生成器,

但是它会返回 0 到 255 之间的一个数字。

因此,函数 在 C 语言编程中

非常重要。

建议您看看 示例文件是

如何设置的,以了解 您该在本课程中如何进行操作。

变量,这张幻灯片, 再加上后面的两张,

可能是本讲座中 最重要的幻灯片了,

因为,它们介绍了 可让您成为一个优秀

程序员的各种变量。

我们可以讨论 重要变量的名称,

而且一个变量的名称应该 阐明这个变量是什么。

但是今天我想要 讨论以下两点。

我想要讨论的是范围, 也就是谁可以访问这个变量。

分配则是关于 这个变量何时存在。

所以,当我们限制 范围时,我们称其为

私有变量,不限制范围时, 则为公有变量。

分配则分为临时 分配和永久性分配。

也就是说, 变量会永久存在吗?

所以,全局变量是一个 具有公有范围的变量,

这意味着任何函数都可以使用它。

如果我定义 这样一个变量,

并将其置于

软件中的 任意位置,

其他模块便可以 将其用作外部变量,

并拥有对这个变量的读写权限。

这是一件很糟糕的事情。

它具有永久性分配,

也就是说,它会永远 存在于存储器内的

某个地址上。

有时,我们确实需要 永久性分配,但是我们

可以不使用全局变量。

这时,我们会创建 一个静态变量。

这里便是一个 静态变量的例子。

它具有永久性分配,

但是其范围是私有的--

针对该文件的私有范围。

因此,只有该文件中的 函数才能访问 m。

其他函数无法将其用作 外部变量并获取该变量。

这种范围限制 将会降低复杂性。

局部变量具有私有 范围和动态分配,

其中动态分配

意味着它临时存储在 寄存器中或堆栈上。

这是一件好事, 因为这样您便可以

重复使用该资源。

这种变量的范围 局限于相应的函数。

例如,这里便有 一个局部变量。

这里是另外 一个局部变量。

它们具有相同的名称, 但是却是不同的

变量。

所以,这个局部变量的 范围是这个函数。

而这个局部变量的 范围是这个函数。

事实证明,我们还需要 考虑局部参数。

这个参数 x 的 访问范围仅限于

这个函数。

所以这是一个局部变量。

因此,还有另外 一种静态变量,

这种静态变量 也具有永久性分配

和私有范围。

但是,如果我将 该静态变量放在这里,

那么它的范围 将仅限于该函数内。

这是一个仅发生一次的 静态变量,它等于 0,

因此,它在存储器的 某个位置上具有

永久性分配,而且会在 启动时初始化为 0。

之后每次 调用该函数,

它都会递增。

所以,您可以看到 它的功能是什么。

它将会对调用

随机数生成器的 次数进行计数,

如果这是 您想要的。

所以,在这里, 我们的工作是

了解范围和分配。

我们想要做的是, 尽量减小范围。

我们对范围的 限制越严格,

我们的系统就越简单。

因此,我们必须了解, 谁实际上需要知道,

或者说访问该变量。

如果我能减小它的范围, 我的系统就会变得更简单。

这个原则同样 适用于分配。

如果我能够动态分配变量, 方法包括将变量置于堆栈上,

在寄存器中使用变量, 或者在 C 代码中将其设置为局部变量,

那么我就可以重复使用该资源。

有时,我真的需要永久性地 记录某些信息,

那么,我将会为其提供 永久性分配。

但是,正如在上一张幻灯片中 所讲的,我希望将其作为静态变量,

以便仅允许相应 文件访问该变量。

跟其他问题 一样,我们也在

几乎每张相关的幻灯片内 反复提及以下问题,

即处理对象是有符号的 还是无符号的。

再说明一次,无符号 C99 变量以 U 开头,

有符号变量 以 Int 开头。

请注意并明确自己 谈论的是 8 位、16 位,

还是 32 位类型。

我们在前面提到过, 其中一种处理方式是

进行升级,因为 您无法将一个有符号

数字与一个 无符号数字相加。

您无法将一个有符号 数字与一个无符号数字

进行比较,但是您 可能又想要这样做。

您将要做的是, 将它们升级到

同一类型,在这一 新类型下执行运算,

然后将其降级 或保持不变。

这向您展示了一种可以 在不丢失信息的情况下

进行升级的方式。

所以,一个 8 位数字可以 成为一个 16 位数字,

一个 8 位数字可以成为一个 16 位或 32 位数字,

无符号 16 位可以成为 无符号和有符号的 32 位,

有符号的 16 位可以 成为有符号的 32 位。

在讨论变量的同时,

让我们来讨论一下 I/O 端口。

I/O 端口 1,输出。

我们将会在后面 详细介绍各个端口,

这里只是想说明 该端口存在于存储器中,

所以其行为方式与变量类似。

当您从德州仪器 获取该端口时,

它存在于一个大家

都知道的地址上。

由于它具有 永久性的地址,

而且大家到知道 具体的位置,

因此,它在形式上和规则上 是一个全局变量。

它支持公共访问, 而且具有永久性分配。

我说过这样很糟糕。

任何软件都可以访问该端口。

但是,为了 降低复杂性,

我们将会从另外一种角度 来看待端口寄存器。

不是关注谁可以访问, 而是关注谁会访问。

如果我们限制谁会访问 我们的端口寄存器,

这将会大大降低 软件的复杂性。

因此,从实践 角度出发,

我们将把 I/O 寄存器视为

类似静态变量的 私有永久性元素。

所以,您对程序中 各元素的范围的

限制越严格,您的 软件就越简单。

常量,我们可以将它们放在这里, 并使用定义语句。

这里又用到了这个 非曲线函数,

我们取输入值, 并计算距离。

该曲线的形状 将通过这两个

数字进行编码。

如果我们有许多常量, 我们可以将它们放在 ROM 中,

这一点可通过 Const 实现。

有时,我们可能想要 使用词语,而不是数字。

这时,您将会 使用枚举类型。

所以,如果机器人--

这是我的机器人, 将沿这条路移动。

这是墙壁。

如果我离右侧 墙壁太近了,

我可能想要说, 我离右侧墙壁太近了,

而不是简单地说 X 等于 2,

虽然这也是在表示离右侧墙壁 太近,我能做的是

使用枚举类型。

这是一个标头,

它实际上 并不是变量,

而只是一个结构。

如果我想要一个变量, 我将会这样做。

比如说 scenario_t, 这是一个类型--

下划线 t 表示这是一个类型。

然后,我将会创建一个变量。

如果我的情况是 离右侧墙壁太近,

我可以将 me 设置为 等于 right too close。

总的来说, 这两者其实代表的是

同一件事情。

只是这可以让您的 代码更易于阅读。

这同样也是一个常量, 我们都知道它的值

将会是 2。

但是,这让我的软件 更加易于阅读。

这便是创建常量的 三种不同的方式。

我们可以讨论您的 行事方式。

如果您有一个 A, 一个 B。执行 A,然后

执行 B,这句话 是一个序列。如果 A 为 true,

那么执行 B,这是条件语句。

执行 A,直至 B,这可能 是一个 while 循环。

我们会看到一些事件, 看到很多中断。

所以,我们可以创建一些触发器。

当您触摸按钮时, 我们可能会

执行某个操作, 比如停止电机转动。

如果撞到墙壁, 则停止电机转动。

这便是一个中断。

我们将会看到的 另外一种中断是,

每秒钟执行 某个操作 100 次。

比如,我们可能会每秒 对传感器采样 100 次。

好的,总结一下, 我们了解了

“与”运算相关的 一些基本问题,

并且我还提醒您注意 处理对象是有符号的

还是无符号的, 是 8 位、16 位,

还是 32 位。

对于变量, 请确保您了解

其范围,也就是谁可以访问, 并且如果可以,

尽可能地将其作为 静态变量或局部变量,

以限制其范围。

然后是关注分配,

也就是变量存在于哪里。

希望您喜欢本课程, 我们下次再见。 501

视频报错
手机看
扫码用手机观看
收藏本课程

相关下载

查看全部

视频简介

TI-RSLK 模块 4 - 讲座视频 - C 语言编程

所属课程:TI机器人系统学习套件(TI-RSLK) 发布时间:2018.08.27 视频集数:69 本节视频时长:26:30

在该模块中,你将会开发和测试迷宫机器人可能会用到的软件功能。

已有7人参与了讨论去论坛跟帖交流
new
关闭广告