--- 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 --- ## 实验简介 在本实验室中,使用简单的数字输入来编程 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 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"); } ```