---
title: ENGR 2350 - 实验 1 - Digital Input/Output (GPIO)
subtitle:
date: 2025-02-10T10:10:37-05:00
lastmod: 2025-02-10T10:10:37-05:00
slug: engr-2350-lab-01
draft: false
author:
  name: James
  link: https://www.jamesflare.com
  email:
  avatar: /site-logo.avif
description: 这篇博客文章提供了ENGR 2350课程第一实验室的指南，重点介绍了使用TI-RSLK机器人进行数字输入/输出（GPIO）的操作。实验包括构建硬件电路、初始化GPIO引脚以及实现控制逻辑以根据用户输入管理电机运动。
keywords: ["C语言","编程","RPI","ENGR 2350"]
license:
comment: true
weight: 0
tags:
  - C语言
  - 编程
  - RPI
  - ENGR 2350
  - Lab
categories:
  - 编程语言
  - Electrical Engineering
collections:
  - ENGR 2350
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: 这篇博客文章提供了ENGR 2350课程第一实验室的指南，重点介绍了使用TI-RSLK机器人进行数字输入/输出（GPIO）的操作。实验包括构建硬件电路、初始化GPIO引脚以及实现控制逻辑以根据用户输入管理电机运动。
resources:
  - name: featured-image
    src: featured-image.jpg
  - name: featured-image-preview
    src: featured-image-preview.jpg
toc: true
math: false
lightgallery: true
password:
message:
repost:
  enable: false
  url:

# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---

<!--more-->

## 实验简介

在本实验室中，使用简单的数字输入来编程 TI-RSLK 使其按照由独立段组成的期望模式运行：

1. 驱动并左转90°
2. 驱动并左转45°
3. 前进
4. 驱动并右转45°
5. 驱动并右转90°
6. 停止1秒。

汽车上的碰撞按钮将用于输入每个连续步骤，而面包板上的滑动开关则会触发已输入的模式执行和/或停止。一个按键可用于删除最后一步输入，另一个按键则可清除所有输入步骤。双色 LED 将指示模式是否正在运行（绿色），模式是否完成（红色）或者程序是否在等待用户输入（关闭）。

### 实验室目标

本实验室旨在进一步加深学生对以下内容的理解：

* Code Composer Studio (CCS) 集成开发环境
* MSP432P401R 微控制器编程，
* 输入和输出的实现与使用，
* 向功能系统发展的模块化 C 程序开发，以及
* TI 机器人系统学习套件（TI-RSLK）的功能。

### 准备

学生应在开始本实验室之前完成活动 1-5。*理解活动 5 的任务对于本实验至关重要。*

### 硬件和工具

- TI-RSLK 机器人汽车
- 面包板
- 组件（提供）：
  - 2x 按键
  - 1x 滑动开关
  - 1x 双色 LED (BiLED)
  - 各种电阻（1x 510Ω，3x 1kΩ）
- 导线（在教室提供）

### 项目模板

{{< link href="lab1_template.zip" content="lab1_template.zip" title="下载 lab1_template.zip" download="lab1_template.zip" card=true >}}

### 原始快照

{{< link href="Laboratory1" content="Laboratory 1: Digital Input_Output (GPIO) — ENGR-2350 documentation.html" title="下载 Laboratory 1: Digital Input_Output (GPIO) — ENGR-2350 documentation.html" download="Laboratory 1: Digital Input_Output (GPIO) — ENGR-2350 documentation.html" card=true >}}

## 第一部分：构建和测试 I/O

本实验室的第一天包括建立所需的硬件，生成 GPIO 初始化代码，并实现测试以验证所有输入和输出的正确操作。所需电路如图 Lab 1 原理图所示。请注意，此原理图提供了 TI-RSLK 上包含的电路以及需要在外部面包板上构建的部分电路。需要在外部面包板上构建的电路应允许其他团队使用同一辆车。

> [!WARNING]
> 在 Lab 1 原理图中没有5V连接！只有3.3V！这是因为微控制器引脚仅支持3.3V，如果施加5V可能会损坏。确保您在连接此微控制器时不要使用5V！

{{< image src="lab1_schematics.svg" width="480px" caption="Lab 1 原理图" >}}

RSLK 上的左、右驱动电机由两条输出线控制：**On/Off** 和 **Direction**。**On/Off** 线将使电机开启（高电平）或关闭（低电平），而 **Direction** 引脚表示轮子是否向前旋转（低电平）或向后旋转（高电平）。

在搭建好电路之后，需要初始化 GPIO 引脚。**您必须使用模板项目** [lab1_template.zip](lab1_template.zip)。此模板项目有一个专门的库([Lab1Lib 驱动程序库](#lab1lib-driver-library-functions))用于与电机交互。在此项目中，使用 **DriverLib** 函数初始化引脚，不要使用 **寄存器**。

> [!IMPORTANT]
> 您必须使用 DriverLib 函数
>
> ```c
> void GPIO_setAsInputPinWithPullUpResistor(uint8_t port,uint8_t pins);
> ```
>
> 来初始化碰撞按钮。这是必需的，因为在原理图上所示的相应电阻 **R_PU** 是微控制器的一部分，并不是一个实际组件。因此，必须通过启用微控制器上的 **R_PU** 来创建所需的 **下拉电阻**。

最后，在 `testIO()` 函数提供的代码中完成测试输入和输出的实现。每次按下键盘键时，应打印输入值。此外，如果按下的键盘键包含在以下列表中的某个位置，则应执行指定的操作以测试输出。模板代码提供了如何完成此操作的例子。
|    Key      | Function                  |
|:-----------:|:-------------------------:|
|     `a`     | LEDFL On                  |
|     `z`     | LEDFL Off                 |
|     `s`     | LEDFR On                  |
|     `x`     | LEDFR off                 |
|     `q`     | BiLED1 Red                |
|     `w`     | BiLED1 Off                |
|     `e`     | BiLED1 Green              |

> [!WARNING]
> 不要忘记在测试时打开汽车！如果不开启汽车，电机将无法工作；然而，所有其他组件都可以正常工作。

要打开/关闭汽车：按下位于车后角标记为“Power”的非常小的按钮。如果汽车内有充满电的电池，则靠近汽车的一个蓝色 LED 会亮起以表示它已开启。

请记得在使用完毕时关闭汽车！

完成第一部分后，请工作人员检查并继续进行第二部分。

## 第二部分：模式输入和运行

在构建、配置和测试了输入和输出之后，可以将模式���制程序添加到项目中。如上文描述所示，每个碰撞按钮用于向模式输入一个特定的动作。它们应被配置为：

- BMP0 添加“右转90°”段
- BMP1 添加“右转45°”段
- BMP2 添加“前进”段
- BMP3 添加“停止1秒”段
- BMP4 添加“左转45°”段
- BMP5 添加“左转90°”段

每当按下碰撞按钮时，会切换 LEDFR 或 LEDFL 的状态，并保持其中一个常亮，以表示已成功添加一个步骤。如果面包板按键被按压，则应从程序中删除最后一个输入的步骤。最后，如果面包板滑动开关从关闭状态切换到开启状态，则程序应该触发模式运行并点亮 BiLED 为绿色。一旦模式完成，BiLED 应该在滑动开关再次关闭之前保持红色亮起。当 BiLED 处于任何颜色时，输入将被忽略。

上面的简单功能描述可以转换成流程图：

{{< image src="lab1_flow.svg" width="480px" caption=" Lab 1 流程图" >}}

带有红色文本的块表示由 [Lab1Lib 驱动程序库](#lab1lib-driver-library-functions)中的函数提供的功能。

> [!WARNING]
> 碰撞按钮容易出现弹跳。您需要去抖您的按键按压/释放和滑动开关开/关检测，以避免单次物理按压被注册为多次按键按压。

在 `controlSystem()` 函数中实现上述控制方案。流程图必须**严格按照执行**；也就是说，不要对未由图表描述的行为做出假设。您需要注释掉 `main()` 函数的 `while(1)` 循环中的 `testIO()` 调用，并取消注释 `controlSystem()` 的调用。

完成第二部分后，请工作人员检查并提交最终代码到 [Gradescope]。该代码必须成功实现上述逻辑，且不易受到按键弹跳的影响。为了演示目的，在教室里可以设置一个简单的障碍课程供汽车通过。

## Lab1Lib 驱动程序库函数

### initSequence

```c
void initSequence(void)
```

此函数初始化 Lab1Lib 驱动程序。

### recordSegment

```c
int8_t recordSegment(int8_t mv)
```

该函数向编程序列中添加一个段。输入 `int8_t mv` 表示要添加的段：

| `mv` 值 |   对应的段  |
|:----------:|:------------------------:|
|     -2     | 驱动并左转90°  |
|     -1     | 驱动并左转45°  |
|      0     | 直行           |
|      1     | 驱动并右转45° |
|      2     | 驱动并右转90° |
|     127    | 停止1秒        |

该函数将输出：

- `0` 如果段已添加到序列中
- `1` 如果段达到最大长度（50），未添加
- `-1` 如果 `mv` 的值不在上面的表中

### popSegment

```c
uint8_t popSegment(void)
```

此函数删除最近添加的一个段。输出 0 表示已擦除一个段，或 1 表示没有段可擦除（序列为空）。

### clearSequence

```c
void clearSequence(void)
```

清除当前编程的序列。

### runSequence

```c
uint8_t runSequence(void)
```

运行编程的序列。返回值为：

- `0` 如果启动成功，
- `1` 如果序列为空，或者
- `2` 如果电机引脚未正确设置。

在返回 2 的情况下，程序还会打印一条消息以指示问题所在。

### statusSegment

```c
int8_t statusSegment(void)
```

返回当前正在运行的段（参见 `record_segment` 表）或如果序列没有运行则返回100。

### statusSequence

```c
uint8_t statusSequence(void)
```

返回正在运行的序列中的步骤编号（如 1,2,3 等），或者如果序列未运行，则返回100。

### getSequenceLength

```c
uint8_t getSequenceLength(void)
```

返回当前编程到序列中的段总数。序列的最大长度为50。

## 我的方法

### 第一部分

第一部分并不难，关键是需要使用各种函数来设置引脚。

对于 `GPIOInit()` 应该看起来像这样：

```c
void GPIOInit(){
    //MP0 ~ BMP5 P4.0, P4.2, P4.3, P4.5, P4.6, P4.7
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN0);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN2);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN3);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN5);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN6);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN7);

    //PB1 P2.4
    //PB2 P2.5
    GPIO_setAsInputPin(GPIO_PORT_P2, GPIO_PIN4);
    GPIO_setAsInputPin(GPIO_PORT_P2, GPIO_PIN5);

    //SS1 P5.6
    GPIO_setAsInputPin(GPIO_PORT_P5, GPIO_PIN6);

    //LEDFL P8.0
    //LEDFR P8.5
    GPIO_setAsOutputPin(GPIO_PORT_P8, GPIO_PIN0);
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);

    GPIO_setAsOutputPin(GPIO_PORT_P8, GPIO_PIN5);
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);

    //BiColor LED P6.0, P6.1
    GPIO_setAsOutputPin(GPIO_PORT_P6, GPIO_PIN0);
    GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN0);

    GPIO_setAsOutputPin(GPIO_PORT_P6, GPIO_PIN1);
    GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN1);

    //左、右电机
    GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN7);
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN7);
    GPIO_setAsOutputPin(GPIO_PORT_P5, GPIO_PIN4);
    GPIO_setOutputLowOnPin(GPIO_PORT_P5, GPIO_PIN4);

    GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN6);
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN6);
    GPIO_setAsOutputPin(GPIO_PORT_P5, GPIO_PIN5);
    GPIO_setOutputLowOnPin(GPIO_PORT_P5, GPIO_PIN5);
}
```

然后，完成 `TestIO()` 的其余部分。

```c
void testIO(){
    while(1){
        uint8_t cmd = getchar();
        if(cmd == 'a'){
            // 打开 LEDL
            GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
            printf("LEDL is on.\n\r");

        }else if(cmd == 'z'){
            // 关闭 LEDL
            GPIO_setOutputLowOnPin(GPIO_PORT_P8,GPIO_PIN0);
            printf("LEDL is off.\n\r");

        }else if(cmd == 's'){
            //打开 LEDR
            GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
            printf("LEDR is on.\n\r");

        } else if (cmd == 'x'){
            //关闭 LEDR
            GPIO_setOutputLowOnPin(GPIO_PORT_P8,GPIO_PIN5);
            printf("LEDR is off.\n\r");

        } else if (cmd == 'q'){
            //将 BiLED 设置为红色
            GPIO_setOutputLowOnPin(GPIO_PORT_P6,GPIO_PIN0);
            GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN1);
            printf("Turn BiLED Red.\n\r");

        } else if (cmd == 'w'){
            //关闭 BiLED
            GPIO_setOutputLowOnPin(GPIO_PORT_P6,GPIO_PIN0 | GPIO_PIN1);
            printf("Turn BiLED off.\n\r");

        } else if (cmd == 'e'){
            //将 BiLED 设置为绿色
            GPIO_setOutputLowOnPin(GPIO_PORT_P6,GPIO_PIN1);
            GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN0);
            printf("Turn BiLED Green.\n\r");
        }
    }
}
```

BMPs 需要使用 `GPIO_setAsInputPinWithPullUpResistor()` 来设置引脚，而 PBs 则需要使用 `GPIO_setAsInputPin()` 来设置引脚。因为已经连接了电阻到 PBs。

### 第二部分

第二部分复杂得多。如果您查看给定的流程图，可能会感到困惑。因此，我重新绘制了流程图，并将其转换为单个循环视图（它会起作用，因为在主程序中有一个无限循环。`controlSystem()` 只需要是一个单循环）

```mermaid
flowchart TB
    A([Start / Infinite Loop]) --> B{Check SS1 State}
    B -- Off --> C[Turn BiLED OFF]
    B -- On --> D{Has Pattern Started?}
    D -- No --> E[Start Pattern]
    D -- Yes --> F{Is Pattern Complete?}
    F -- No --> G[Turn BiLED GREEN]
    F -- Yes --> H[Turn BiLED RED]
    C --> I{Any BMPx Pressed?}
    I -- Yes --> J[Add Segment to Pattern]
    J --> K[Toggle LEDFR/L States]
    K --> L[Wait For All Buttons To Be Released]
    L --> M{PB1 Pressed?}
    I -- No --> M
    M -- Yes --> N[Remove Last Segment]
    N --> L[Wait For All Buttons To Be Released]
    M -- No --> P{PB2 Pressed?}
    P -- Yes --> Q[Delete Entire Pattern]
    Q --> L[Wait For All Buttons To Be Released]
    P -- No --> L
```

之后，我们可以更容易地创建我们的函数。(完整的 main.c 在页面底部)

```c
void controlSystem(void){
    // 用于跟踪序列是否当前正在运行（以便我们知道
    // 它刚刚完成，或者根本没有开始等）
    static uint8_t patternRunning = 0;
    static uint8_t runSeq = 1;

    uint8_t ss_state = GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN6);

    if (ss_state == GPIO_INPUT_PIN_HIGH){
        // 如果 SS1 打开
        printf("SS1 is on\n\r");
        if (statusSequence() == 100){
            // 如果序列完成或为空
            uint8_t seqLength = getSequenceLength();
            if (seqLength == 0){
                // 如果序列为空
                patternRunning = 1;
                printf("sequence is empty, won't start\n\r");
            } else {
                // 如果序列不为空
                uint8_t seqCallback;
                if (runSeq == 1){
                    seqCallback = runSequence();
                    runSeq = 0;
                }
                if (seqCallback == 0){
                    // 如果序列启动成功
                    printf("sequence started OK\n\r");
                } else if (seqCallback == 1){
                    // 如果序列为空
                    printf("sequence is empty\n\r");
                } else if (seqCallback == 2){
                    // 如果 GPIO 错误
                    printf("fail to start sequence\n\r");
                }
            }
        }
        if (patternRunning){
            // 如果序列正在运行
            if (statusSequence() == 100){
                // 如果序列完成 => 将 BiLED 设置为红色
                GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN0);
                GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN1);
            } else{
                // 如果序列正在运行 => 将 BiLED 设置为绿色
                GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN0);
                GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN1);
            }
        }
    } else{
        // 如果 SS1 关闭
        patternRunning = 0;
        // 将 BiLED 设置为关闭状态
        GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN0 | GPIO_PIN1);

        // 静态切换变量，用于跟踪下次成功记录段时哪个前 LED 应该亮起
        static uint8_t toggleLED = 0;

        if (runSeq == 0){
            runSeq = 1;
            clearSequence();
        }

        // 单独检查每个碰撞按钮
        if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(2);  // 右转90°
            if (recStatus == 0) {
                printf("Turn right 90 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                    printf("Right LED toggled.\r\n");
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN2) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN2) == GPIO_INPUT_PIN_LOW) {}
            _delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(1);  // 右转45°
            if (recStatus == 0) {
                printf("Turn right 45 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN3) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN3) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(0);  // 前进
            if (recStatus == 0) {
                printf("Drive forward added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN5) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN5) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(127);  // 停止1秒
            if (recStatus == 0) {
                printf("Stop for 1 s added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN6) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN6) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(-1);  // 左转45°
            if (recStatus == 0) {
                printf("Turn left 45 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN7) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN7) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(-2);  // 左转90°
            if (recStatus == 0) {
                printf("Turn left 90 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        // -- 检查 PB1（弹出最后一步）--
        if(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN4) == GPIO_INPUT_PIN_LOW)
        {
            // 等待释放
            while(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN4) == GPIO_INPUT_PIN_LOW){}
            __delay_cycles(200000);

            uint8_t popRes = popSegment();
            if(popRes == 0)
            {
                printf("Last segment popped.\r\n");
            }
            else
            {
                printf("No segment to pop.\r\n");
            }
        }

        // -- 检查 PB2（清除整个序列）--
        if(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN5) == GPIO_INPUT_PIN_LOW)
        {
            // 等待释放
            while(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN5) == GPIO_INPUT_PIN_LOW){}
            __delay_cycles(200000);

            clearSequence();
            printf("Entire sequence cleared.\r\n");
        }
    }
}
```

这里有一些技巧。首先，BMPs 在释放微动开关时会弹跳。没有去抖的话，在一秒钟内会产生超过50次点击。为了去抖，我使用了一个空循环等待其被释放，并且延迟以等待弹跳结束。

```diff
if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {
+    while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {}
+    __delay_cycles(240e3);
    //...
}
```

其次，确保 `runSequence()` 只在需要时运行一次。否则汽车将永远无法停止。我的解决方案是创建一个全局变量来跟踪这一点并保持更新。

```diff
void controlSystem(void){
+   static uint8_t runSeq = 1;
    if (ss_state == GPIO_INPUT_PIN_HIGH){
        // 如果 SS1 打开
        if (statusSequence() == 100){
          // 如果序列完成或为空
          if (seqLength == 0){
              // 如果序列为空
          } else {
              // 如果序列不为空
+             if (runSeq == 1){
                  seqCallback = runSequence();
+                 runSeq = 0;
              }
              //...
          }
        }
        //...
    } else{
        // 如果 SS1 关闭
+       if (runSeq == 0){
+           runSeq = 1;
+           clearSequence();
+       }
        //...
    }
}
```

最后，根据函数返回的状态码打印足够的调试信息和错误日志。

### 最终代码

```c
#include "engr2350_msp432.h"
#include "lab1lib.h"
#include <stdlib.h>

void GPIOInit();
void testIO();
void controlSystem();

uint8_t LEDFL = 0; // 两个变量��于存储前左/右LED（车上的）的状态
uint8_t LEDFR = 0;

int main() {

    sysInit(); // 基本汽车初始化
    initSequence(); // 初始化 lab1Lib 驱动程序
    GPIOInit();

    printf("\r\n\n"
           "===========\r\n"
           "Lab 1 Begin\r\n"
           "===========\r\n");

    while(1){
        //testIO(); // 在第一部分中用于测试 I/O
        controlSystem(); // 在第二部分中实现所需的功能
    }
}

void GPIOInit(){
    //MP0 ~ BMP5 P4.0, P4.2, P4.3, P4.5, P4.6, P4.7
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN0);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN2);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN3);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN5);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN6);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN7);

    //PB1 P2.4
    //PB2 P2.5
    GPIO_setAsInputPin(GPIO_PORT_P2, GPIO_PIN4);
    GPIO_setAsInputPin(GPIO_PORT_P2, GPIO_PIN5);

    //SS1 P5.6
    GPIO_setAsInputPin(GPIO_PORT_P5, GPIO_PIN6);

    //LEDFL P8.0
    //LEDFR P8.5
    GPIO_setAsOutputPin(GPIO_PORT_P8, GPIO_PIN0);
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);

    GPIO_setAsOutputPin(GPIO_PORT_P8, GPIO_PIN5);
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);

    //BiColor LED P6.0, P6.1
    GPIO_setAsOutputPin(GPIO_PORT_P6, GPIO_PIN0);
    GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN0);

    GPIO_setAsOutputPin(GPIO_PORT_P6, GPIO_PIN1);
    GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN1);

    //左、右电机
    GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN7);
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN7);
    GPIO_setAsOutputPin(GPIO_PORT_P5, GPIO_PIN4);
    GPIO_setOutputLowOnPin(GPIO_PORT_P5, GPIO_PIN4);

    GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN6);
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN6);
    GPIO_setAsOutputPin(GPIO_PORT_P5, GPIO_PIN5);
    GPIO_setOutputLowOnPin(GPIO_PORT_P5, GPIO_PIN5);
}

void testIO(){
    while(1){
        uint8_t cmd = getchar();
        if(cmd == 'a'){
            // 打开 LEDL
            GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
            printf("LEDL is on.\n\r");

        }else if(cmd == 'z'){
            // 关闭 LEDL
            GPIO_setOutputLowOnPin(GPIO_PORT_P8,GPIO_PIN0);
            printf("LEDL is off.\n\r");

        }else if(cmd == 's'){
            //打开 LEDR
            GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
            printf("LEDR is on.\n\r");

        } else if (cmd == 'x'){
            //关闭 LEDR
            GPIO_setOutputLowOnPin(GPIO_PORT_P8,GPIO_PIN5);
            printf("LEDR is off.\n\r");

        } else if (cmd == 'q'){
            //将 BiLED 设置为红色
            GPIO_setOutputLowOnPin(GPIO_PORT_P6,GPIO_PIN0);
            GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN1);
            printf("Turn BiLED Red.\n\r");

        } else if (cmd == 'w'){
            //关闭 BiLED
            GPIO_setOutputLowOnPin(GPIO_PORT_P6,GPIO_PIN0 | GPIO_PIN1);
            printf("Turn BiLED off.\n\r");

        } else if (cmd == 'e'){
            //将 BiLED 设置为绿色
            GPIO_setOutputLowOnPin(GPIO_PORT_P6,GPIO_PIN1);
            GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN0);
            printf("Turn BiLED Green.\n\r");
        }
    }
}

void controlSystem(void){
    // 用于跟踪序列是否当前正在运行（以便我们知道
    // 如果刚刚完成，或者根本没有开始等）
    static uint8_t patternRunning = 0;
    static uint8_t runSeq = 1;

    uint8_t ss_state = GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN6);

    if (ss_state == GPIO_INPUT_PIN_HIGH){
        // 如果 SS1 打开
        printf("SS1 is on\n\r");
        if (statusSequence() == 100){
            // 如果序列完成或为空
            uint8_t seqLength = getSequenceLength();
            if (seqLength == 0){
                // 如果序列为空
                patternRunning = 1;
                printf("sequence is empty, won't start\n\r");
            } else {
                // 如果序列不为空
                uint8_t seqCallback;
                if (runSeq == 1){
                    seqCallback = runSequence();
                    runSeq = 0;
                }
                if (seqCallback == 0){
                    // 如果序列启动成功
                    printf("sequence started OK\n\r");
                } else if (seqCallback == 1){
                    // 如果序列为空
                    printf("sequence is empty\n\r");
                } else if (seqCallback == 2){
                    // 如果 GPIO 错误
                    printf("fail to start sequence\n\r");
                }
            }
        }
        if (patternRunning){
            // 如果序列正在运行
            if (statusSequence() == 100){
                // 如果序列完成 => 将 BiLED 设置为红色
                GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN0);
                GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN1);
            } else{
                // 如果序列正在运行 => 将 BiLED 设置为绿色
                GPIO_setOutputHighOnPin(GPIO_PORT_P6, GPIO_PIN0);
                GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN1);
            }
        }
    } else{
        // 如果 SS1 关闭
        patternRunning = 0;
        // 将 BiLED 设置为关闭状态
        GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN0 | GPIO_PIN1);

        // 静态切换变量，用于跟踪下次成功记录段时哪个前 LED 应该亮起
        static uint8_t toggleLED = 0;

        if (runSeq == 0){
            runSeq = 1;
            clearSequence();
        }

        // 单独检查每个碰撞按钮
        if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(2);  // 右转90°
            if (recStatus == 0) {
                printf("Turn right 90 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                    printf("Right LED toggled.\r\n");
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN2) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN2) == GPIO_INPUT_PIN_LOW) {}
            _delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(1);  // 右转45°
            if (recStatus == 0) {
                printf("Turn right 45 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN3) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN3) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(0);  // 前进
            if (recStatus == 0) {
                printf("Drive forward added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN5) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN5) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(127);  // 停止1秒
            if (recStatus == 0) {
                printf("Stop for 1 s added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN6) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN6) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(-1);  // 左转45°
            if (recStatus == 0) {
                printf("Turn left 45 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        else if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN7) == GPIO_INPUT_PIN_LOW) {
            // 等待释放
            while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN7) == GPIO_INPUT_PIN_LOW) {}
            __delay_cycles(240e3); // ~简单去抖延迟

            // 记录请求的段
            int8_t recStatus = recordSegment(-2);  // 左转90°
            if (recStatus == 0) {
                printf("Turn left 90 added.\r\n");
                // 切换前 LED 状态
                if (toggleLED == 0) {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    toggleLED = 1;
                    printf("Left LED toggled.\r\n");
                } else {
                    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
                    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN5);
                    toggleLED = 0;
                }
            } else if (recStatus == 1) {
                printf("ERROR: Sequence reached max length (50). Segment not added.\r\n");
            } else if (recStatus == -1) {
                printf("ERROR: mv value not recognized by recordSegment.\r\n");
            }
        }
        // -- 检查 PB1（弹出最后一步��--
        if(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN4) == GPIO_INPUT_PIN_LOW)
        {
            // 等待释放
            while(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN4) == GPIO_INPUT_PIN_LOW){}
            __delay_cycles(200000);

            uint8_t popRes = popSegment();
            if(popRes == 0)
            {
                printf("Last segment popped.\r\n");
            }
            else
            {
                printf("No segment to pop.\r\n");
            }
        }

        // -- 检查 PB2（清除整个序列）--
        if(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN5) == GPIO_INPUT_PIN_LOW)
        {
            // 等待释放
            while(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN5) == GPIO_INPUT_PIN_LOW){}
            __delay_cycles(200000);

            clearSequence();
            printf("Entire sequence cleared.\r\n");
        }
    }
}
```

## 可改进的部分

首先，有很多重复代码。例如，每次有开关被按下时都会应用去抖逻辑。我可以将它们打包成一个函数并多次调用。

```diff
+void debouncePin(uint8_t port, uint16_t pin) {
+    while (GPIO_getInputPinValue(port, pin) == GPIO_INPUT_PIN_LOW) {}
+    __delay_cycles(240000); 
+}

if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {
-    while (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {}
-    __delay_cycles(240e3);
+    debouncePin(GPIO_PORT_P4, GPIO_PIN0);
    // ... do the rest ...
}
```

此外，我们可以将更多代码移入函数。例如，“记录段”检查可以放入一个函数中。

```c
int8_t recStatus = recordSegment(2);  // 或其他值
if (recStatus == 0) {
    // ...
} else if (recStatus == 1) {
    // ...
} else if (recStatus == -1) {
    // ...
}
```

因为这些检查和消息重复出现，我们可以创建一个辅助函数。

```c
void handleSegmentRecord(int8_t mv, uint8_t *toggleLED, const char *message)
{
    int8_t recStatus = recordSegment(mv);
    if (recStatus == 0) {
        printf("%s added.\r\n", message);
        toggleFrontLEDs(toggleLED);
    } else if (recStatus == 1) {
        printf("ERROR: Sequence reached max length.\r\n");
    } else if (recStatus == -1) {
        printf("ERROR: mv value not recognized.\r\n");
    }
}
```

然后我们可以这样写：

```c
if (GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == GPIO_INPUT_PIN_LOW) {
    debouncePin(GPIO_PORT_P4, GPIO_PIN0);
    handleSegmentRecord(2, &toggleLED, "Turn right 90");
}
```
