跳转至

02 综合应用开发

考频:★★★★★(综合大题,官方样题 61 题全解析)


一、S3C2410 开发背景

S3C2410 是三星(Samsung)出品的 ARM920T 内核处理器,是嵌入式教学中最常见的实验平台。

属性 参数
CPU 内核 ARM920T(ARMv4T)
工作频率 最高 203MHz
内存接口 SDRAM
GPIO 多组通用 I/O 口(GPA、GPB、GPD、GPE…)
常见外设 LCD、键盘、串口、USB、网口

GPIO 寄存器操作基础

每组 GPIO 有两个核心寄存器: - GPxCON:控制寄存器,配置每个引脚的功能(输入/输出/特殊功能) - GPxDAT:数据寄存器,读取输入值或写入输出值

配置编码(2位/引脚):

编码 含义
00 输入(Input)
01 输出(Output)
10 特殊功能
11 特殊功能

二、裸机开发(无操作系统)

裸机开发指不运行任何操作系统,直接在硬件上跑应用程序。

裸机程序基本结构

#include "2410addr.h"   // S3C2410 寄存器地址头文件

int main(void) {
    /* 硬件初始化 */
    InitClock();     // 初始化时钟
    InitUART();      // 初始化串口(调试用)
    InitMemory();    // 初始化内存

    /* 应用主循环 */
    while (1) {
        /* 读取按键 */
        /* 控制 LED */
        /* 其他逻辑 */
    }
    return 0;
}

三、μC/OS-II 环境下开发

在 μC/OS-II 上开发,需要将任务组织成 RTOS 任务。

#include "includes.h"

OS_STK Task1Stk[512];
OS_STK Task2Stk[512];

void Task1(void *pdata) {
    InitHardware();       // 在任务里初始化硬件
    InitTimerTick();      // 在 OSStart() 之后激活时钟节拍!
    while (1) {
        DoWork1();
        OSTimeDly(100);   // 延时 100 个节拍,让出 CPU
    }
}

void Task2(void *pdata) {
    while (1) {
        DoWork2();
        OSTimeDly(200);
    }
}

int main(void) {
    OSInit();
    OSTaskCreate(Task1, (void *)0, &Task1Stk[511], 5);
    OSTaskCreate(Task2, (void *)0, &Task2Stk[511], 6);
    OSStart();
    return 0;
}

四、键盘扫描函数详解(官方样题重点!)

考频:★★★★★(综合题 13~18 空全考)

键盘矩阵原理

矩阵键盘用"行列扫描"方式检测按键: - 行线:连接 GPIO 输出引脚(逐行拉低) - 列线:连接 GPIO 输入引脚(读取哪列为低)

以 4×3 矩阵键盘(4行3列)为例: - 行线:GPE0、GPE1、GPE2、GPE3(4根输出) - 列线:GPD0、GPD1、GPD2(3根输入)

扫描思路:依次让每一行输出低电平(其余行高电平),然后读取所有列的值,如果某列为低电平,说明该行该列对应的按键被按下。


官方样题键盘扫描代码解析

INT16U ScanKey() {
    int i;
    INT8U temp;

    /*
     * 第 (13) 空:初始化 GPD0~GPD2 为输入模式
     *
     * GPD0~GPD2 是列线,需要配置为"输入"(编码 00)
     * 每个引脚占用 GPDxCON 的 2 位:
     *   GPD0 → 位[1:0]
     *   GPD1 → 位[3:2]
     *   GPD2 → 位[5:4]
     * 共 6 位(3引脚 × 2位)需要清零
     * 清掉低 6 位的掩码:~(0x3F) = 0xFFFFFFC0
     */
    rGPDCON = rGPDCON & 0xFFFFFFC0;   // (13) 答案:0xFFFFFFC0

    /*
     * 第 (14) 空:初始化 GPE0~GPE3 为输出模式
     *
     * GPE0~GPE3 是行线,需要配置为"输出"(编码 01)
     * 每个引脚占用 GPExCON 的 2 位:
     *   GPE0 → 位[1:0] = 01 → 0x01
     *   GPE1 → 位[3:2] = 01 → 0x04
     *   GPE2 → 位[5:4] = 01 → 0x10
     *   GPE3 → 位[7:6] = 01 → 0x40
     * 合并:0x01 | 0x04 | 0x10 | 0x40 = 0x55
     * 保留高位原值:掩码 0xFFFFFF00
     */
    rGPECON = (rGPECON & 0xFFFFFF00) | 0x00000055;
    //                    ↑(14)=0xFFFFFF00

    /*
     * 第 (15) 空:扫描循环控制值
     *
     * 需要扫描 4 行,i 依次取 1, 2, 4, 8(每次左移1位)
     * 循环条件:i <= 8
     * 即:for (i=1; i<=8; i<<=1)
     */
    for (i = 1; i <= 8; i <<= 1)   // (15) 答案:8
    {
        /* 将当前行拉低(取反后输出),其他行保持高电平 */
        rGPEDAT = ~i & 0x0F;

        /* 读取列线状态(GPD0~GPD2 共3位) */
        temp = rGPDDAT & 0x07;

        /*
         * 第 (16) 空:判断无按键的条件
         *
         * 3根列线全为高电平 = 无按键
         * 3位全1 = 0x07
         * 所以 (temp & 0x07) == 0x07 表示无按键
         */
        if ((temp & 0x07) != 0x07)   // (16) 答案:0x07
        {
            /* 有按键按下!构造返回值 */
            /*
             * 返回值格式:高8位=行信息,低8位=列信息
             * 行信息:~i(当前扫描行的行号取反)
             * 列信息:temp | 0xF8(将高5位填1,只保留低3位列信息)
             */
            return ((~i) << 8) | (temp | 0xF8);
        }
    }

    /*
     * 第 (17) 空:无任何按键按下时的返回值
     *
     * 扫描完所有行都没有检测到按键,返回 0xFFFF 表示无键
     */
    return 0xFFFF;   // (17) 答案:0xFFFF
}

各空答案汇总

空号 答案 原因说明
(13) 0xFFFFFFC0 清除 GPD0~GPD2 的 6 位配置(清低 6 位),配置为输入
(14) 0xFFFFFF00 保留 GPEDCON 的高 24 位,低 8 位重新配置(GPE0~GPE3 输出)
(15) 8 扫描 4 行,i 依次为 1, 2, 4, 8,循环到 i<=8
(16) 0x07 3 列全高(0b111 = 0x07)= 无按键
(17) 0xFFFF 无任何按键按下的特殊返回值(全1)

第 (18) 空:计算按键返回值

例题:若第 3 行第 2 列的按键被按下,ScanKey() 的返回值是多少?

解题步骤:

Step 1:确定行号对应的 i 值

循环中 i 的取值: - 第 1 行:i = 1(0b0001) - 第 2 行:i = 2(0b0010) - 第 3 行:i = 4(0b0100) - 第 4 行:i = 8(0b1000)

所以第 3 行对应 i = 4。

Step 2:计算行信息(高 8 位)

~i = ~4 = ~0b00000100 = 0b11111011 = 0xFB
高8位 = (~i) << 8 = 0xFB << 8 = 0xFB00

Step 3:确定列号对应的 temp 值

列线是 GPD0、GPD1、GPD2,对应 temp 的位 0、1、2: - 第 1 列:GPD0 = 0,temp 位 0 = 0 - 第 2 列:GPD1 = 0,temp 位 1 = 0 - 第 3 列:GPD2 = 0,temp 位 2 = 0

第 2 列按下,GPD1(位1)= 0,其余列为高:

temp & 0x07 = 0b101 = 0x05
(第2列GPD1=0,第1列GPD0=1,第3列GPD2=1)

Step 4:计算列信息(低 8 位)

temp | 0xF8 = 0x05 | 0xF8 = 0xFD
(将高5位填1,保留低3位列状态)
低8位 = 0xFD

Step 5:合并返回值

返回值 = 高8位 | 低8位 = 0xFB00 | 0xFD = 0xFBFD

(18) 答案:0xFBFD


五、官方样题 61 题完整解析(综合题框架)

题目背景

以 S3C2410 为核心的嵌入式系统,运行 μC/OS-II,实现键盘控制功能。

第 19 空(μC/OS-II 创建任务函数)

int main(void) {
    OSInit();
    _______(TaskKey, (void *)0, &KeyStk[STK_SIZE-1], 10);  // 第19空
    _______(TaskDisp, (void *)0, &DispStk[STK_SIZE-1], 11);
    OSStart();
    return 0;
}

答案:OSTaskCreate

第 20 空(启动多任务调度)

int main(void) {
    OSInit();
    OSTaskCreate(TaskKey, (void *)0, &KeyStk[STK_SIZE-1], 10);
    OSTaskCreate(TaskDisp, (void *)0, &DispStk[STK_SIZE-1], 11);
    _______;   // 第20空
    return 0;
}

答案:OSStart()


六、解题技巧总结

[!TIP] 键盘扫描题解题步骤

  1. 看GPIO配置:哪些脚做输入(列线),哪些脚做输出(行线)
  2. 算掩码:清除/设置对应位,注意每个引脚占 2 位
  3. 看循环条件:i 的最大值 = 最后一行对应的值(4行就是8)
  4. 看判断条件:全高电平 = 无键,用 0x07(3列)或 0x0F(4列)
  5. 算返回值(~行号) << 8 | (列读值 | 0xF8)

[!NOTE] μC/OS-II 综合题固定套路

OSInit()OSTaskCreate() × N → OSStart()

时钟节拍初始化写在某个任务函数里(OSStart 之后执行)。


本章小结

开发方式:
  裸机开发 → 直接操作寄存器,简单循环
  μC/OS-II → OSInit + OSTaskCreate + OSStart

GPIO 配置规律:
  输入 = 00,输出 = 01(每引脚2位)
  清N位掩码 = ~((1<<(2N))-1)

键盘扫描关键:
  行线输出(逐行拉低),列线输入(读取)
  无键 = 所有列为1 = 0x07(3列)
  有键返回:(~行) << 8 | (列值 | 0xF8)
  无键返回:0xFFFF

综合题必填:
  第19空 = OSTaskCreate
  第20空 = OSStart

真题演练

以下是NCRE三级嵌入式真题中与本节相关的典型考题:

1. 下列关于嵌入式系统的软件结构的描述中,错误的是( )。

  • A)最简单的软件结构是轮询结构
  • B)选择嵌入式系统软件结构的基本原则是:尽可能选择通用操作系统+应用软件结构,降低应用系统设计的复杂度
  • C)小型的监控式嵌入式操作系统通常只包含内核,仅实现任务调度、任务间通信和中断管理等最基本功能
  • D)带中断的轮询结构也称为前后台结构
答案解析

答:B 选择嵌入式系统软件结构的基本原则是:可以满足响应时间需求的最简单的结构,而不是"尽可能选择通用操作系统"。嵌入式系统设计最重要的一点是满足系统对功能、成本、功耗、尺寸等方面的具体需求,软件结构并没有优劣之分,需要根据具体应用需求选择。


2. 下面关于ADS1.2开发工具的说法中,不正确的是( )。

  • A)利用ADS1.2工具建立的应用工程项目中,至少应包括一个Release生成目标
  • B)ADS1.2工具完成应用程序连接时,可按照Scattered类型的文件进行地址映射
  • C)ADS1.2工具把被编译后的信息,按照RO段或RW段或ZI段来存储
  • D)对于地址映射关系简单的工程项目,ADS1.2可以根据Simple连接类型的参数来完成应用程序的地址映射
答案解析

答:A 在ADS1.2的集成开发环境中,一个工程项目中至少应包含一个生成目标(不是特指Release)。ARM提供的可执行输出文件的模板包括了3个生成目标:Debug、Release、DebugRel。A项说"至少应包括一个Release生成目标"表述不准确。


3. 【填空题】嵌入式系统开发过程中,需求分析阶段需要对用户的功能需求进行分析。验证模块/系统的功能和性能,发现错误是_的目的。分析所发现的错误,检查错误原因,定位故障位置和进行修改是_的目的。

答案

答:测试(test);调试(debug) 测试的目的是验证模块/系统的功能和性能,同时发现错误;调试的任务是分析测试中发现的错误,检查原因,定位故障(错误)位置,采取适当措施修改软硬件,然后返回重新进行测试。