本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Keil μVision,即ARM Keil,是一款在单片机开发领域广泛使用的集成开发环境。本教程将指导如何运用Keil软件编写和调试单片机程序。内容包括Keil的安装与启动、创建新项目、添加和编写源代码、配置芯片选项、编译链接、调试设置以及高级特性介绍。教程旨在帮助学习者深入理解并熟练使用Keil这款强大的开发工具,提高单片机编程的效率和质量。
keil教程详解 关于如何使用keil软件来进行单片机程序编写

1. Keil软件介绍与安装

1.1 Keil软件概述

Keil软件是由Keil Elektronik GmbH公司开发的嵌入式系统开发环境,提供集成开发环境(IDE)、编译器、调试器等工具,专为8位、16位、32位微控制器(MCU)设计,广泛应用于ARM Cortex-M系列、8051等微控制器的开发。它支持高效的程序编写、编译、调试、仿真等功能,是嵌入式系统工程师不可或缺的开发工具之一。

1.2 Keil的特性与优势

Keil提供了丰富的调试支持、实时分析和追踪功能,以及友好的用户界面,极大地简化了嵌入式软件的开发流程。它的集成性允许开发者在一个统一的平台完成从代码编写到硬件调试的全部工作,减少了学习成本和开发时间。

1.3 Keil的安装流程

安装Keil软件的过程通常如下:
1. 从Keil官网下载适用于您操作系统的安装包。
2. 运行安装包,遵循安装向导指示进行安装。
3. 安装过程中可能需要选择安装组件,如MDK-ARM开发环境、目标仿真器驱动等。
4. 完成安装后,启动Keil,完成注册和配置许可(如果需要)。

安装后,建议进行简单的配置,如更改工作目录、安装设备包等,以适应开发需求。接下来,我们可以进入创建新项目与配置的章节,展开我们的嵌入式开发之旅。

2. 创建新项目与配置

2.1 新项目的基本设置

2.1.1 选择目标设备和工具链

在使用Keil软件创建新项目时,首先需要选择合适的目标设备和工具链。目标设备指的是我们将要编程的微控制器(MCU)型号,它决定了程序将要运行的硬件平台。而工具链则包括了编译器、汇编器、链接器等工具,这些工具负责将我们的源代码转换成可在目标设备上运行的机器代码。

在Keil中,可以按照以下步骤选择目标设备和工具链:

  1. 打开Keil uVision软件,选择菜单栏中的“Project” > “New uVision Project”。
  2. 在弹出的“Select Project”对话框中,输入项目名称,并选择合适的存储路径。
  3. 点击“Save”后,将会进入“Select Device for Target”对话框。
  4. 在该对话框中,使用左侧的分类树或者搜索框来定位到特定的微控制器型号。
  5. 在选中目标设备后,选择适合该设备的工具链。Keil支持ARM、Cortex-M等不同架构的工具链,确保选择正确的编译器版本与设备相匹配。

确保目标设备与工具链的正确匹配,是确保项目开发顺利进行的关键一步。不同的设备有不同的内存大小、外设特性等,正确的工具链则保证了代码的正确编译与链接。

2.1.2 设置项目名称和存储路径

设置项目名称和存储路径是项目的初始设置之一,它为项目的后续开发和管理提供了基础。在Keil uVision中进行如下操作:

  1. 在“Project” > “New uVision Project”后,输入项目名称,例如“MyFirstProject”。
  2. 点击“Save”按钮,这时会弹出“Select Device for Target”对话框。
  3. 在该对话框中选择目标设备和工具链后,点击“OK”。
  4. 之后,系统会提示你保存.uvoptx文件,该文件包含了项目的所有设置信息。
  5. 接着,选择合适的文件夹来存储该项目的所有文件。建议使用清晰的文件夹结构,以方便管理和维护。

例如,可以在项目根目录下创建以下几个子目录:
- Source 文件夹用来存放所有源代码文件。
- Header 文件夹用来存放所有头文件。
- Object 文件夹用来存放编译过程中生成的目标文件。
- Listing 文件夹用来存放编译生成的列表文件。
- Output 文件夹用来存放最终生成的可执行文件。

通过以上步骤设置好项目名称和存储路径后,为项目的后续开发打下了良好的基础。这种组织结构使得项目文件井井有条,有助于提高开发效率和后期维护。

2.2 项目结构与文件管理

2.2.1 新建文件夹和文件

在Keil中,良好的文件管理习惯不仅可以让项目结构清晰,还可以提高团队协作效率。创建项目后,需要手动创建新的文件夹和文件来组织项目结构。

  1. 在项目资源管理器中,右键点击项目名称,选择“Add Group…”创建新的文件夹。例如,可以创建以下文件夹: Source Headers Object Listing Output
  2. 在新建的文件夹上右键,选择“Add New Item to Group ‘文件夹名’…”创建新文件。
  3. 选择合适的文件类型,比如C文件( .c)或汇编文件( .s)。

建立文件夹和文件的步骤如下:

右键项目名 > Add Group... > 命名新文件夹(例如:Source)
右键 Source 文件夹 > Add New Item to Group 'Source'... > C File
重复上述步骤添加其他文件或文件夹

这些文件夹不仅帮助你按文件类型和功能分类管理,而且它们在源代码控制中也非常有用,特别是在大型项目或团队合作中。

2.2.2 管理项目中的文件和文件夹

管理项目文件和文件夹是确保项目可维护性的关键。在Keil中可以灵活地添加、删除和重命名文件和文件夹。

  • 添加文件或文件夹

    在项目资源管理器中右键点击要添加的父文件夹,选择“Add New Item to Group ‘父文件夹名’…”来添加新文件,或者选择“Add New Group to Group ‘父文件夹名’…”来添加新的子文件夹。

  • 删除文件或文件夹

    在项目资源管理器中选中要删除的文件或文件夹,然后按键盘上的 Delete 键或右键点击选择“Remove”即可删除。

  • 重命名文件或文件夹

    在项目资源管理器中选中要重命名的文件或文件夹,然后直接在该名称上点击二次(不是双击,而是快速点击两次),即可进入编辑状态,更改名称。

重要的是,以上操作都会即时反映到项目文件夹结构中,确保源代码的组织结构与项目管理器中的结构保持一致。

2.3 项目配置选项

2.3.1 了解项目选项的作用

项目配置选项为项目提供了详尽的设置,涵盖了从编译选项到运行时环境的各种设置。理解这些配置选项,可以帮助你根据项目需求,对项目进行微调以优化开发和运行效率。

在Keil中,项目配置选项通常可以通过项目属性窗口来访问。步骤如下:

  1. 双击项目资源管理器中的项目名称,打开项目属性窗口。
  2. 在项目属性窗口中,可以看到左侧的选项树,包括“Target”、“C/C++”、“Assemble”、“Output”等多个类别。
  3. 点击不同的类别,可以在右侧看到相关的选项及其说明。

这些选项包括目标设备的具体配置(如时钟频率、电源管理等)、编译器的优化级别、输出文件的格式等。

2.3.2 修改项目设置以适应不同需求

在Keil中,为了满足不同开发需求,你可能需要对项目设置进行修改,例如调整编译器优化级别、添加额外的编译定义、修改目标设备的配置等。

修改项目设置的步骤通常如下:

  1. 在项目资源管理器中,双击目标名称(通常位于项目的最上方),打开目标属性。
  2. 根据需要修改设置。例如,如果你希望提高编译速度,可以设置“C/C++”选项卡下的优化级别为“Optimize for Speed”。
  3. 如果需要添加特定的编译定义,可以在“C/C++”选项卡下的“Define”输入框中添加宏定义。
  4. 对于目标设备的特定设置,可以在“Target”选项卡下找到与设备相关的配置项。
  5. 在完成修改后,记得点击“OK”或“Apply”按钮来保存设置。

不同的项目和开发阶段可能需要不同的配置,适当的修改可以使项目更加灵活和高效。

3. 源代码文件的添加与编写

3.1 编写C语言源代码

3.1.1 掌握单片机C语言编程基础

单片机的C语言编程是嵌入式开发中的核心技术。与传统PC编程不同,单片机编程需要考虑到硬件资源的限制,如内存、存储空间和处理能力。在编写代码时,开发者需要理解单片机的内部结构,包括寄存器、中断系统、时钟管理、外设接口等。

编写单片机C语言代码的第一步是设置开发环境,如选择合适的编译器和工具链。Keil提供了多种编译器,如ARM编译器、GCC编译器等。选择完毕后,就可以开始编写C语言程序了。

示例代码如下:

#include <REGX51.H> // 包含51系列单片机的寄存器定义

void delay(unsigned int ms) {
    unsigned int i, j;
    for (i = ms; i > 0; i--)
        for (j = 120; j > 0; j--); // 空循环用于延时
}

void main() {
    while (1) {
        P1 = 0xFF; // 将端口P1所有位设为高电平
        delay(1000); // 延时大约1秒
        P1 = 0x00; // 将端口P1所有位设为低电平
        delay(1000); // 延时大约1秒
    }
}

3.1.2 实践基本的输入输出操作

在单片机编程中,输入输出操作是基础。这通常涉及到单片机的I/O端口。在51单片机中,例如,P0、P1、P2、P3是一些常见的I/O端口。通过设置这些端口的电平状态,可以实现LED灯的开关、按键状态读取等。

下面代码示例演示了如何控制一个LED灯的闪烁:

#include <REGX51.H>

void delay(unsigned int ms) {
    // ... 同上延时函数
}

void main() {
    while (1) {
        P1 = ~P1; // 切换P1端口所有引脚的状态
        delay(1000); // 延时大约1秒
    }
}

在这个例子中, ~P1 操作将P1端口的所有位进行取反操作,如果之前是低电平,现在变成高电平,反之亦然。这个操作通常会导致LED灯闪烁,假设LED正极连接在P1端口,并且通过限流电阻接地。

3.2 汇编语言文件的添加与编写

3.2.1 汇编语言基础

汇编语言与机器语言非常接近,它为程序员提供了一种几乎与硬件直接对话的方式。汇编语言编程允许开发者利用单片机的全部潜能,但同时也需要深入了解硬件的工作原理和指令集。

汇编语言代码通常用来实现一些对性能要求极高的代码片段,或者在资源限制非常苛刻的场合下使用。下面的汇编代码示例是51单片机实现延时的简单程序:

ORG 00H ; 程序起始地址为00H
MAIN: 
    MOV P1, #0FFH ; 将P1端口设置为高电平
    ACALL DELAY   ; 调用延时子程序
    MOV P1, #0    ; 将P1端口设置为低电平
    ACALL DELAY   ; 再次调用延时子程序
    SJMP MAIN     ; 无限循环

; 延时子程序
DELAY: 
    MOV R2, #20    ; 外循环计数器,计数20次
OUTER_LOOP: 
    MOV R1, #250   ; 内循环计数器,计数250次
INNER_LOOP: 
    DJNZ R1, INNER_LOOP ; 内循环递减计数,直到为0
    DJNZ R2, OUTER_LOOP ; 外循环递减计数,直到为0
    RET            ; 返回调用点

END ; 程序结束

3.2.2 在项目中整合汇编代码

在Keil项目中添加汇编源文件,首先需要新建一个汇编文件(例如,使用 .s .asm 扩展名),然后编写汇编代码。在Keil项目设置中,需要指定汇编器的配置,以确保汇编文件可以被正确编译。

一旦编写完成,便可以像在其他高级语言中一样调用汇编编写的函数或子程序。请注意,直接操作硬件的代码通常会用汇编语言编写,以获得最大的执行效率。

3.3 源代码的组织与模块化

3.3.1 文件的组织结构

随着项目复杂性的增加,代码的组织和模块化变得至关重要。在Keil项目中,可以创建多个源文件(.c/.s/.cpp)和头文件(.h),并将相关的代码和函数组织在一起。

例如,可以创建一个名为 main.c 的文件作为程序的入口点,然后创建其他文件如 led.c button.c ,分别包含LED控制和按键读取相关的函数。这样做可以提高代码的可读性和可维护性。

下面是一个简化的项目目录结构示例:

ProjectName/
    ├── main.c       # 主程序文件
    ├── led.c        # LED操作相关函数
    ├── led.h        # LED操作相关函数声明
    ├── button.c     # 按键操作相关函数
    ├── button.h     # 按键操作相关函数声明
    └── Keil Project Files

3.3.2 使用模块化编程提高代码复用

模块化编程指的是将大型程序拆分成小型、独立的模块或组件,每个模块负责程序的一个小部分。在单片机开发中,模块化编程有助于代码复用、降低复杂性,以及提高开发效率。

例如,可以创建一个通用的延时模块,然后在需要延时的任何地方调用它,而不是重复编写延时代码。这样,如果将来需要修改延时逻辑,只需在一个地方进行,而不是在代码中多处修改。

下面是一个简单的模块化延时函数示例:

// delay.h 文件内容
#ifndef _DELAY_H_
#define _DELAY_H_

void delay(unsigned int ms); // 延时函数声明

#endif

// delay.c 文件内容
#include "delay.h"
#include <REGX51.H>

void delay(unsigned int ms) {
    // ... 延时函数实现
}

// main.c 文件内容
#include "delay.h"

void main() {
    while (1) {
        P1 = 0xFF;
        delay(1000);
        P1 = 0x00;
        delay(1000);
    }
}

通过这种方式, delay 函数就可以在其他地方被复用,而不需要复制代码。模块化编程还促进了代码的封装和抽象,为软件工程实践提供了基础。

4. 芯片选项的配置

4.1 芯片特定的配置选项

4.1.1 时钟设置和电源管理

在嵌入式系统开发中,正确配置微控制器的时钟系统和电源管理模块对于确保系统稳定运行至关重要。微控制器的时钟系统负责为CPU、外设提供时钟信号,其设置决定了系统的运行速度和功耗。在Keil MDK开发环境中,芯片特定的时钟配置选项通常包含以下几个方面:

  • 时钟源选择: 可以选择内部RC振荡器、外部晶体振荡器或外部时钟源。不同的时钟源有不同的精度和稳定性。
  • 时钟分频设置: 时钟分频器用来调整时钟频率,以满足CPU和外设的需要。不当的分频设置可能导致系统性能下降或外设无法正常工作。
  • 时钟树配置: 复杂的微控制器可能有多个时钟树,需要合理配置以分配资源。

在Keil中进行时钟设置,开发者需要进入项目的“Options for Target”对话框,在“Target”选项卡中选择时钟配置。例如,对于ARM Cortex-M系列微控制器,通常在“System”部分进行时钟配置:

flowchart LR
    A[Target Options] --> B[Target]
    B --> C[Clock]
    C --> D[Source]
    C --> E[Prescaler]
    C --> F[Clock Tree]

4.1.2 外设的配置和初始化

微控制器的外设(如ADC、UART、I2C、SPI等)在使用前通常需要进行初始化设置。这包括配置外设的工作模式、时序参数、中断请求等。正确配置外设是确保数据准确、稳定传输的基础。

在Keil中配置外设,开发者可以通过“Manage Run-Time Environment”对话框来启用和配置外设。以下是一个配置UART的例子:

#include "stm32f4xx.h"

void UART_Configuration(void)
{
    // 1. Enable clock for UART
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    // 2. Configure the UART parameters
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART2, &USART_InitStructure);

    // 3. Enable UART
    USART_Cmd(USART2, ENABLE);
}

int main(void)
{
    UART_Configuration();
    // Rest of the code...
}

在上面的代码中,首先启动了UART2的时钟。然后,初始化了UART2的配置参数,并且最后启动了UART2。需要注意的是,代码中使用了STM32F4的HAL库函数,这些函数对寄存器操作进行了封装,以便开发者更加便捷地进行配置。

4.2 中断系统的配置

4.2.1 中断向量表的设置

中断向量表是中断服务程序入口地址的列表,微控制器在接收到中断请求时,会根据中断向量表中的信息找到相应的中断服务程序进行处理。在Keil MDK中,中断向量表通常由启动文件(Startup file)自动配置,该文件的名称通常与微控制器型号有关,例如STM32F4的启动文件是 startup_stm32f40xx.s

中断向量表的设置需要遵循特定的微控制器架构要求。例如,在ARM Cortex-M架构中,中断向量表的第一项通常是复位向量(Reset Vector),紧接着是NMI(非可屏蔽中断)和硬fault(硬件故障)等向量。

4.2.2 中断优先级和模式配置

中断优先级定义了在多个中断同时发生时,哪个中断应该优先得到处理。在Keil MDK中,中断优先级的配置可以通过“NVIC_PriorityGroupConfig”函数设置优先级分组,然后通过“NVIC_Init”函数配置每个中断的优先级。

void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    // Configure the priority grouping: 2 bits for pre-emption priority
    // 2 bits for subpriority
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    // Configure the User Button interrupt priority
    NVIC_InitStructure.NVIC_IRQChannel = USER_BUTTON_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

在上述代码中,首先配置了中断优先级分组,然后配置了“USER_BUTTON_IRQn”中断的优先级,将其设置为中等优先级。

4.3 存储器的配置

4.3.1 RAM和ROM的分配

在嵌入式系统开发中,存储器的分配对于程序的运行至关重要。RAM用于运行时存储变量和临时数据,而ROM用于存储程序代码和常量数据。在Keil中,存储器的分配一般在链接器脚本(Linker Script)中进行配置,该文件定义了程序的内存布局。

在链接器脚本中,开发者可以指定不同段(section)的内存位置。例如:

MEMORY
{
    FLASH (x) : ORIGIN = 0x08000000, LENGTH = 128K
    RAM (x) : ORIGIN = 0x20000000, LENGTH = 32K
}

SECTIONS
{
    .text : { *(.text*) } > FLASH
    .data : { *(.data*) } > RAM
    .bss : { *(.bss*) } > RAM
}

在这个例子中,定义了两个存储器区域:FLASH和RAM。 .text 段,包含程序代码,被分配到FLASH中; .data .bss 段,包含初始化和未初始化的数据,被分配到RAM中。

4.3.2 各类存储器配置的特殊要求

不同的微控制器对存储器配置有不同的要求。例如,在Cortex-M系列微控制器中,需要特别注意存储器的对齐问题。对齐是性能和稳定性的重要因素。例如,某些微控制器可能要求32位数据必须在4字节边界上对齐。

在Keil中,可以通过链接器选项设置对齐要求。例如:

-Wl,-T,MY_APP.ld -Wl,-Map,MY_APP.map

在这个链接器命令中,“MY_APP.ld”是链接器脚本文件,它可以在里面定义所需的对齐限制。开发者需要根据具体微控制器的硬件手册,来设置适当的对齐参数。

在配置存储器时,还需要考虑性能和资源利用率的平衡。在空间资源有限的嵌入式系统中,开发者需要合理地规划代码和数据的存储,确保不会超出可用的存储资源,同时也要避免不必要的内存浪费。这通常需要开发者有深入的系统架构知识和优化经验。

通过合理配置RAM和ROM,以及满足存储器的特殊要求,开发者可以确保程序高效且稳定地运行,同时合理利用宝贵的硬件资源。

5. 编译与链接过程

5.1 编译过程详解

5.1.1 编译器的作用和设置

编译器在将源代码转换成可执行文件的过程中扮演着至关重要的角色。它通过语法分析,将人类可读的代码转换为机器可以理解的指令。在Keil环境中,编译器的设置主要通过项目属性进行配置,可以调整优化级别、警告级别以及定义特定的预处理宏等。

在配置编译器时,一个重要的步骤是选择正确的优化级别。优化级别决定了编译器在代码优化时的考虑因素,从简单的代码大小优化到执行速度的优先,每一种优化策略都会对最终生成的二进制文件产生不同的影响。

5.1.2 编译错误的诊断与修复

在编译过程中,编译器会检查源代码的语法错误。当遇到错误时,编译器会停止编译并报告问题。错误信息通常包括错误类型、位置以及可能的修复建议。理解这些错误信息是开发过程中不可或缺的一部分。

修复编译错误的一个好方法是从第一个出现的错误开始处理。许多情况下,一个错误可能会导致后续多个错误,因此解决首个错误后,其他错误可能会自动消失。对于复杂的错误,查阅文档、搜索网络或咨询同事都可能是获取解决方案的有效途径。

5.2 链接过程详解

5.2.1 链接器的作用和设置

链接器的作用是将编译后的多个目标文件以及必要的库文件链接成一个单一的可执行文件。链接器会解析程序中的符号引用,保证所有必要的函数和数据都能被正确地放置在最终的可执行文件中。

在Keil中,链接器的设置同样在项目属性中配置。可以指定输出文件的格式、内存布局、静态库的使用等。正确配置链接器设置对于确保程序按照预期工作是十分重要的。

5.2.2 常见链接错误及解决方法

链接过程中可能会遇到的错误包括但不限于“多重定义的符号”、“未解析的符号”等。多重定义错误通常是因为在不同的文件中定义了相同的全局变量或函数。未解析的符号错误则是由于找不到某些符号的定义。

解决这些链接错误的方法是仔细检查项目的源文件和头文件,确保全局变量和函数只在一个地方定义,并且在需要的地方正确地声明和包含头文件。此外,确保所有必要的库文件都被添加到链接器的配置中。

5.3 调试信息和符号的使用

5.3.1 调试信息的生成和使用

调试信息对于后续的调试工作至关重要,它记录了源代码行号、变量名和符号信息等。在Keil的编译设置中,需要确保启用了调试信息的生成选项。

调试信息使得在使用Keil的调试器时能够观察到变量值的变化,单步执行代码,并且在发生异常时能够准确地定位到代码行。

5.3.2 符号表的生成和应用

符号表是一个包含所有编译后程序符号信息的映射表,它帮助链接器将符号引用与相应的内存地址关联起来。符号表同样对于调试有重要的作用,它使得调试器能够将内存地址映射回源代码中的符号。

生成符号表时,需要在编译选项中启用生成符号表的选项。在调试过程中,通过符号表可以实现从内存地址到源代码的反向查找,这对于发现和解决问题非常有帮助。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Keil μVision,即ARM Keil,是一款在单片机开发领域广泛使用的集成开发环境。本教程将指导如何运用Keil软件编写和调试单片机程序。内容包括Keil的安装与启动、创建新项目、添加和编写源代码、配置芯片选项、编译链接、调试设置以及高级特性介绍。教程旨在帮助学习者深入理解并熟练使用Keil这款强大的开发工具,提高单片机编程的效率和质量。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐