65.9K
CodeProject 正在变化。 阅读更多。
Home

AVRILOS:AVR 微控制器的简单操作系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (34投票s)

2010年11月13日

CDDL

24分钟阅读

viewsIcon

95607

downloadIcon

4190

一种轮询式操作系统(无抢占式多任务处理),支持快速应用程序开发

摘要

一个嵌入式系统简单操作系统框架,允许快速开发针对AVR系列的应用,但可以很容易地移植到其他架构。在本文中,我将描述这个操作系统的概念和结构,并提供一个示例应用程序,以便读者理解轻松构建新事物的简单性。

相关链接

引言

嵌入式系统是一个非常有趣的领域。你可以使用硬件和软件做一些让每个人都惊叹不已的事情,除非这种美被很好地隐藏起来。伴随着它们的问题、限制和特殊要求,如果你重复构建这样的系统,每个设计都会有一些共同点。例如,我总是使用系统滴答计时器和UART。这就是为什么我选择构建一个操作系统核心框架,它能让我更快地构建应用程序,而不会过于复杂。这个操作系统不进行抢占式多任务处理。相反,它是一个轮询协作系统,即每个任务要么做一些事情,要么在等待新数据时退出(不阻塞)。它非常简单,内存占用非常小,而且你可以非常容易地添加或删除组件,随时在你的下一个项目中使用它。有了这个操作系统,我可以非常快速地开发我的小型应用程序,因为当我已经拥有了基本运行条件时,唯一缺少的部分就是我的纯粹应用程序:我需要做什么,这通常是一个或两个页面的程序。我可以在几天内编写并使其运行起来……

背景

当我开始使用嵌入式系统时,我编写汇编语言,我们使用带 EPROM 作为程序内存、RAM 极少的微控制器。在每个项目中,我几乎都必须使用系统时钟计时器,并且经常使用 UART 与主机 PC 进行通信。大约在 90 年代末,随着 AVR 的引入,我转向了 ISP(系统内编程)。不再需要笨重的 EPROM 擦除器,几秒钟内即可轻松编程等。然而,那时内存仍然受限。在将 8051 代码移植到 AVR 的过程中,我需要一些东西。一个系统时钟计时器和一个 UART。我的下一个问题是调试。尽管 Atmel 有一个 AVR 模拟器,但当需要从外部环境获取输入时,我无法测试我的应用程序。因此,为了更容易调试,我构建了一个占用空间非常小的监视器:只需读/写端口、内存并操作外部外设。这个调试器是任何新构建的组成部分。后来我转向 C 语言,因此我再次将大部分代码用 C 重写。此外,随着我添加越来越多的外设,而程序内存又很宝贵,我开始配置我的微型内核,通过 #defines 来添加或删除组件。最终结果是拥有一个允许我快速构建应用程序的平台。我只需所有基础设施支持,然后专注于构建实际的应用程序。我只对特定代码片段使用模拟器(更多是为了查看实际的 C 语句做了什么)。在调试期间,我使用我的监视器、串口的 `printf`,当然还有万用表、示波器以及硬件方面的 LED(!)如果需要的话。

此外,由于我使用 GCC 进行编译,所以我不使用 IDE 进行 AVR 开发。我使用 makefile 来构建和配置我的构建,并使用我喜欢的编辑器进行编码。因此,这个框架理论上可以移植到任何微控制器(例如 PIC、ARM 等)。事实上,我已将变体移植到 ARM 和 ColdFire 处理器/控制器。当然,你可以自由使用你喜欢的 IDE。

由于内存有限,而且我不需要抢占式多任务的复杂性,所以这个操作系统的理念是每个任务检查是否有输入要处理,如果有要做的事情就执行,否则就返回到轮询主循环。优点是它非常可扩展,你无需担心复杂的事情,它对 RAM 非常高效,而且没有上下文切换,因此可以节省执行时间。缺点当然是,最坏情况下的执行(所有任务都执行)应该足够小(最好小于系统滴答周期),但这取决于你的应用程序!你可能会偶尔打破这个规则。但是,我避免这样做,并且我相信对于大多数项目来说,这个循环的时间不应该成为问题。

描述

项目目标

该项目的目标是提供一个可扩展并允许快速应用程序开发的平台。我最终用 C 语言编写了它,因此在某种程度上它是可移植的。你需要为每个新处理器编写主要外设,这是主要的难点。但是,在核心系统启动并运行后,你可以从这个结构中受益,将其作为基础项目来构建新的应用程序。由于我最初是为 AVR 构建的,所以我将其命名为“AVRILOS”:**AVR** **IL**ias **O**perating **S**ystem(AVR 伊利亚斯操作系统)。我假设你已经准备好 AVR 硬件。为了方便你,并且由于各种 I/O 映射到特定的端口(尽管你可以轻松更改它们),我提供了我的基本原理图,它或多或少地在各个项目中复制(像 AVRILOS 一样),并根据我的特定问题进行添加。

工具

我用于 AVRILOS 的工具是

  1. (软件) WinAVR (Windows 上的 AVR GCC)。
  2. (软件) Atmel AVR Studio (用于仿真)。
  3. (软件) 你偏好的编辑器。
  4. (软件) 终端程序 (例如 Terminal, PuTTY)。
  5. (软件) 编程器软件 (AVRDude 已包含在 WinAVR 中,但如果你愿意,可以使用 AVREAL32)。
  6. (硬件) 你的控制器所在地的硬件板!
  7. (硬件) 编程器加密狗。
  8. (硬件) USB/RS232-TTL 串口电平转换器,用于连接到显示器。

我选择性地使用了一些额外的工具

  1. (软件) CVTFPGA(用于将 Xilinx Spartan FPGA 的串行比特流集成到我的代码中,稍后详细介绍)
  2. (软件) Hexbin3
  3. GNUWIN32(如果我不使用 WinAVR,例如其他编译器包如 MPLAB,则用于 makefile)
  4. (软件) Python 和 Python Wx 用于构建主机应用程序。
  5. (硬件) 示波器(推荐)
  6. (硬件) 万用表(至少也要有!)
  7. 任何你能想象并适合的。

AVRILOS

目录结构

目录结构如下:

有两个主要目录,HWSW

  1. HW 是我所有硬件开发完成的目录。这包括板级原理图和 PCB 文件以及 FPGA 设计。
  2. SW 是软件目录,其中包含所用处理器的目录名,这样多年后我才知道每个项目使用了哪个处理器。此外,我可能会在这里放置主机软件(在另一个名为 host 的目录中)。

让我们专注于 avr16 的目录结构。AVR16 指的是 ATMega16 AVR。你可以随意命名。因为编译器倾向于生成许多中间文件,我不希望它们干扰我的源文件。因此,我特意为此包含了三个目录

  1. build.dep:这里放置 C 文件的依赖项。这些是由 makefile 自动生成的。
  2. build.err:我在这里指示编译器放置任何错误文件,以便在需要时进行跟踪。
  3. build.obj:我在这里放置每个模块的目标文件。还有最终的 .elf 文件。从 MAP 文件中我可以找到任何变量的内存位置,并将其用于监视器中进行检查。
  4. build.lst:每个模块的列表文件。
  5. build.rom:最终的编程文件,可用于设备编程。

根 makefile (名为 makefile) 位于 avr16 的根目录。

cfg 目录包含所有卫星 makefile。这些文件包含配置选项、编译器命令等。

src 目录是您所期望的。源文件。其中包含:

  1. Applic:主文件 Kernel.c,其中包含“调度器”。初始化和主循环都在这里。我的特定应用程序 C 文件也放在这里。
  2. peripheint:微控制器的内部外设。这些是定时器、UART 等。
  3. periphext:微控制器外部的外设。这些可能是智能卡、LPC 闪存、SPI 设备等。
  4. utils:包含许多类型转换工具(hex2binbin2hex)、延迟等。当然,如果内存空间足够,你也可以使用 stdlib 的 sprintf
  5. debug:这里有我的监视器调试器,此外还有一个扩展调试文件,其中我为可能使用的外部外设单独启用或禁用函数。如果我不使用它们,我就禁用相应的函数,并节省一些内存空间。
  6. include:这里有全局定义和设置。如果可编程,还包括每个使用的外设的引脚/端口分配。

内核描述

Kernel.c 包含初始化代码和主循环。在启动期间,内核执行每个模块/外设的各种初始化。没有一个初始化文件适用于所有模块。相反,每个模块通过读写修改指令修改其 I/O 寄存器中的位。这样,每个外设就不会相互干扰,除非存在端口冲突。这允许更模块化的设计以及轻松添加/移除模块。

调度器以轮询方式执行我们需要的任务。每个任务检查是否应因某个标志(如 SysTick 定时器(在 periphint/Timer0.c 中))而被触发

if ( ( v_SysStat & (1 << b_SysTick) ) != 0)  

检查定时器中断是否已在 `v_SysStat` (变量) 中设置了 `b_SysTick` (位) 标志,如果没有则退出。如果设置了,则执行所有需要的定时器功能。

另一种情况是测试串口中是否有新数据(就像 `applic/serial/SerApp.c` 所做的那样),如果没有则退出。

我几乎总是包含的模块有:`SysTick`(执行毫秒级软件定时器、LED 状态指示等)、`debugger`(用于调试的监视器)、`SysADC`(按顺序捕获所有 8 个通道,因此应用程序只需读取 ADC 数据的内存位置)、`SerApp`(一个小型串行命令应用程序)。我们还可以选择让应用程序在每个主循环中运行,或者选择每 n 次运行一次应用程序(即 LED 闪烁时)。

if ( ( v_SysStat & (1 << b_AppTick) ) != 0) 

这允许应用程序使用简单的计数器作为延迟或超时,因为修改(增/减)操作是在每个 SysTick 中完成的。当然,稍作修改,您可以为 LedAlive 和应用程序设置不同的步调。

最后,对于低功耗应用,我在循环末尾添加了一个睡眠命令。如果没有活动中断,系统将进入低功耗模式,直到发生事件。

因此,要创建新任务(应用程序、设备等),你需要知道新元素不应长时间阻塞系统。具体来说,所有函数的执行延迟不应超过 SysTick 定时。如果超过了,你有两种选择:要么增加 SysTick 间隔,要么减少阻塞时间。

根据我的经验,到目前为止,我还没有需要为任何项目调整这些时间。

我使用的另一个概念是我的中断例程是最小化的。例如,`timer0` 中断只是设置一个标志然后退出。主循环将执行 `SysTick`(延迟处理程序),后者将完成所有繁重的工作。当然,中断可能会更复杂,比如将数据放入 FIFO 的串行中断。但其思想是避免在中断级别进行任何主要处理。因此,中断阻塞的可能性将最小化。

此外,我在中断和延迟处理程序之间使用简单的生产者-消费者通信。我通过原子操作或单向动作(只写/只读变量)检查背景中每个变量的修改不会受到中断的影响。这些将在 UART 模块中可见。

模块描述

模块:SysTick

`SysTick` 模块执行所有主要的定时功能。它使“活跃”LED 闪烁,触发 ADC 开始新的转换,触发 LCM 延迟结束,触发键盘扫描功能,并且还包含软件定时器。

这些触发器是位于 `v_SysStat` 中的简单标志(位)。你可以通过修改文件开头的 *includes/ifc_time.h* 常量来轻松更改应用程序时间间隔。

 // Alive Led Indications Set
#define  c_ALIVEOK_ms  250  /* Alive LED When Ok */
#define  c_ALIVESER_ms  500  /* Alive LED When Serial Error */

// Timed Tasks interval
#define c_AppInterval_ms  8     
#define c_ADCInterval_ms  32
#define c_KEYInterval_ms  16 

对应的激活位定义在 *includes/settings.h* 中。

/*************** SysStat Register ******************/
#define b_SysTick				0
#define b_DBG					1
#define b_AppTick				2
#define b_ADCTick				3
#define b_LCDBusy				4 

我省略了未由 `Systick` 定时器激活的标志。标志的清除由 *Kernel.c* 主循环完成。键盘扫描和 LCD 操作直接由 Systick 调用,因此 `v_SysStat` 中没有这些的标志。

由于这些常量以毫秒为单位引用,你可以根据需要进行更改。CPU 时钟频率在 *cfg/hw.in* makefile 中定义,所有计时应立即符合,除非你需要不同的预分频器(稍后在同一文件中设置为 CLK/64)。

/* Select Clock Source for T0 */
#define c_T0CLK 	c_CLK64
#define	c_T0DIV		64

此外,您可以使用 `f_SystickSetErrLevel` 函数提供动态错误指示。此函数修改闪烁 LED(Alive)的间隔(以滴答为单位),以便您可以通过更改 LED Alive 的闪烁间隔来通知用户出现问题/异常。例如,在串行通信错误的情况下,您可以从应用程序中调用

f_SystickSetErrLevel(c_ALIVESER_ticks);  

`c_ALIVESER_ticks` 在 *include/ifc_time.h* 中稍后定义,并由同一文件开头的相应常量 `ALIVESER_ms` 派生。

那么,当您需要一个定时器间隔来执行通用超时或其他任何操作时,该怎么办?很简单!SysTick 提供了可编程的(`MAXSWTIMERS`)数量的软定时器,以及一些用于方便引用的附加定义。

// Maximum SW Timers (mS)
#define c_MAXSWTIMERS	4
#define c_SwTimer0      0   /* Timer Activation (0: Stop, >0: Run) */
#define c_SwTimer1      1
#define c_SwTimer2      2
#define c_SwTimer3      3 

这些计数器以毫秒为单位计数。要启动一个定时器,你需要编写

buf_SwTimer[SwTimer0] = 10;  

这会启动软定时器 0,超时值为 10 毫秒。

查看定时器是否到期

If (buf_SwTimer[SwTimer0] == 0) Action_Timer0_finished();
提前停止计时器
buf_SwTimer[SwTimer0] = 0;

此外,您可能需要一些小的延迟函数(仅几微秒)。在这种情况下,您可以在 *utils/delay.c* 中找到一些阻塞函数。这些函数可以在初始化期间使用,当您等待外设启动时。例如,我在启动时进行 FPGA 代码配置时就使用这样的延迟。或者,您也可以在 SPI 组件上使用微秒延迟。

模块:UART

UART 是我最喜欢的外设。由于我的大多数应用程序不需要直接使用 UART,所以我的硬件中没有添加 TTL-RS232 电平转换器来连接到我的 PC。鉴于我经常手工制作电路板,我不想为仅在开发期间有用(用于我的监视器或应用程序)的功能浪费元件、导线和时间。所以我所做的,就是在标准(当然是我的标准)排针上放置 Tx/Rx 信号以及电源和地,然后使用适配器(加密狗)连接到我的 PC。这个加密狗可以是一个简单的电平转换器(这就是为什么我需要在排针上供电),或者现在,由于每台计算机都使用 USB 端口,我使用 USB-串口转换器(例如我的 Polulu AVR 编程器提供了这个附加功能)。现在,每个电路板都有这个带有 3-4 根线的排针,我就搞定了。

UART 具有简单的 I/O 功能。要进行初始化,请调用 f_ConfigSerial。波特率在 makefile (hw.in) 中定义。其余设置是标准的 8N1。如果需要更改它们,则必须更改 uart.c 中此函数的源代码。

在硬件层面,我们支持多种处理器,并且 Tx/Rx 都使用软件 FIFO。FIFO 的大小在 *includes/settings.h* 中定义

/*************** UART Buffer Sizes ******************/
#define c_RXBUFLEN	16
#define c_TXBUFLEN	64

如你所见,Tx FIFO 更大。我这样做是为了能够从应用程序响应一个较大的 Tx 字符串而不阻塞系统。如果 FIFO 小于发送到主机的较大字符串,那么 Put String 函数将等待(并阻塞在那里),直到它可以将所有数据写入 FIFO。

主要接口函数是:

bool f_Uart_PutChar(INT8 c);
INT16 f_Uart_GetChar(void);
bool f_Uart_PutStr(INT8 s[]);

f_Uart_PutChar:将字符放入 TxFIFO 中,除非 FIFO 已满。返回代码表示成功(1:成功,0:失败)。它不会阻塞系统。

f_Uart_GetChar:检查是否有可用字符,如果有,则将其从 Rx FIFO 中移除并返回给应用程序(-1 [0xFFFF]:失败,0x00XX:收到字符)。它不会阻塞系统。

f_Uart_PutStr:向串口发送一个以空字符结尾的字符串。总是成功。如果 Tx FIFO 小于字符串,此函数可能会阻塞系统。

如果你需要一个 `printf` 函数,你可以使用 `sprintf` 函数将数据写入缓冲区,然后将其发送到 `f_Uart_PutStr`。或者你也可以构建自己的 `printf` 函数。

为了最小化占用空间,我使用位于 *utils/typeconv.c* 中的简单转换函数。

模块:调试器

虽然在本文中我称之为“调试器”,但实际上我指的是一个监视器。我构建的调试器/监视器不接管执行控制,不单步执行指令,也不做任何普通调试器会做的花哨事情。它更像一个监视器。你可以在运行时更改变量(不中断程序执行),你可以检查内存、端口、I/O 或写入这些外设。你还可以操作其他设备,如 SPI、LPC 或 FPGA,如果你在监视器中添加了此功能。

基本功能总结如下:

命令与格式

摘要

R XXXX 

读取地址 XXXX 处的字节

W XXXX YY 

将字节 YY 写入 XXXX

V XXXX 

查看从 XXXX 开始的 4 字节十六进制值

A XXXX 

查看从 XXXX 开始的 4 字节 ASCII 值

1/2/3/4  

PINA/B/C/D

B XX YY 

写入端口 PortX(01-04), DDRX(11-14) YY

b XX 

读取端口 PortX(01-04), DDRX(11-14)

Q 0X 

读取模拟端口 0X (X: 0-7)

I XXXX 

检查地址 XXXX 处的 EEPROM 数据

P XXXX YY

将字节 YY 写入地址 XXXX 处的 EEPROM

U ?????????

用户命令

L 00XX

读取 LCM 地址 XX

C XX

写入 LCM 命令 XX

D XX

写入 LCM 数据 XX

S XXXXXXXX

读取 LPC 总线地址 XXXXXXXX-X+4 处的 4 字节 (32 位)

s XXXXXXXX YY

在地址 XXXXXXXX (32 位) 写入 LPC 字节 YY

t XXXXXXXX YY

在 LPC FLASH SST49F020/A 写入一个字节(带写保护)

*

恢复到串口应用。禁用调试器

P 

读取 SPI DR, SR

F 00XX

读取 FPGA 寄存器 XX(自定义命令,取决于 FPGA 代码)

f 00XX YY 

在寄存器 XX 写入 FPGA 字节 YY(自定义命令,取决于 FPGA 代码)

**注意:**所有数字均为十六进制格式,并且应为大写(区分大小写)。

例如,要检查 RAM 地址 0x10 处的变量,请在串行终端中键入

R 0010 

如果你需要将此内存位置的内容修改为 0x55 的值

W 0010 55

如果你需要检查 AVR 的 PIN A,只需键入“1”,端口 A 的引脚状态就会返回。

变量地址可以在 */build.lst/kernel.map* 文件中找到。例如,你可以搜索 `v_SysStat` 地址并找到类似以下内容:

*(COMMON)*

COMMON 0x00800160 0x2 build.obj/kernel.o
0x00800160 v_SysStat
0x00800161 v_StatReg

所以 AVR 的 RAM 地址是 0x160。

你可以在 `dbgext.c` 中提供自己的命令,但你还需要修改基础的 `debugger.c` 文件来获取输入并处理新命令。我更喜欢在单独的 `dbgext.c` 中实现额外命令,同时在 `debugger.c` 中添加命令识别和解析。然后,我根据 `HW.in` makefile 模块定义在需要时启用或禁用相应的命令。额外命令的功能始终存在于 `debugger.c` 中,它调用 `dbgext.c` 的函数。

此外,还有一个空的 user 命令 ('`U`'),你可以在每个应用程序中以不同方式实现它,而无需进行更复杂的 `dbgext.c` 修改。

调试器模块不使用 `sprintf` 或 `stdio` 库,因此占用空间最小。在内存更多的处理器上,你可以实现一个更好、功能更强大的监视器,但一个占用空间小的监视器可以在任何地方使用。

其他模块

我包含了 LCM 和 keymat 4x4 模块。然而,这些模块经过部分测试,可能并非总能正常工作。特别是 LCM_char 模块是基于 Joerg Wunsch 针对 HD44780 控制器编写的代码。在另一篇文章中,我们将讨论 FPGA SPI 编程以及如何从 *.bit* 文件生成 C 代码的流程。

系统设置

为了让 make 文件工作,你需要调整 *env.in* 文件。在这里你需要识别编译器可执行文件、编程器等。你需要将可执行文件放在你的路径中,否则你可能需要添加工具的绝对路径。

Cfg/Srcobj.in 根据 hw.in 文件的定义来确定将使用哪些源文件/目标文件。

Cfg/Srcdef.inhw.in makefile 变量定义转换为 C 文件使用的 `#define` 预处理器变量。

最后,cfg/hw.in 配置所有硬件参数(即晶体时钟频率、波特率、活动模块等)。

要编译代码,请在 makefile 所在的根目录打开命令提示符。然后输入“`make`”或“`make all`”。要进行新的构建或修改 *hw.in* 文件时,需要先进行新的干净构建:“`make clean`”,然后“`make`”。

您可以在编译过程中看到各种消息。如果出现错误,编译器会停止并指出特定文件中存在问题的行。

当 rom 文件准备就绪后,你可以输入“`make prog`”或“`make progsp`”将程序下载到你的目标设备。prog 选项用于 AVReal 编程器,而 progsp 则使用 avr-dude,这是标准的 gcc 编程工具。

输入“`make size`”可以查看目标大小。

输入“`make list`”可以查看可用选项。

硬件引脚控制都设置在 *includes/settings.h* 文件中。我所有的硬件设备都使用引用此文件的宏定义。因此,我将所有 I/O 集中在一个地方,并且对于每个硬件,我只需要更改此文件。当然,对于 UART 或 SPI 等特定外设,由于功能是硬连接的,存在一些限制。

在下图中,您可以看到一个编译示例。

示例应用程序

我们的示例应用程序将实现一个锁。钥匙将是一个简单的智能卡,并控制一个舵机进行锁定。系统的输入是有效的智能卡(具有正确的序列号),输出是控制 R/C 舵机锁定/解锁的 PWM 信号。

有关智能卡的更多详细信息,请访问

http://support.gateway.com/s/Mobile/SHARED/FAQs/1014330Rfaq21.shtml

在此应用中,我使用了用于电话应用(卡式电话)的简单 PROM 型智能卡。在下图中,您可以看到读卡器(用于连接卡触点到 PCB 的无源元件)和智能卡的引脚。

在下面的图中,你可以看到舵机锁的机械配置。

下面的有限状态图展示了应用程序逻辑控制是如何实现的。

主要有四种状态。最初,系统进入 `IDLE` 状态。在插入有效的智能卡之前,系统一直等待。插入代码中预定义的有效卡后,系统激活舵机并等待预定时间,直到 R/C 舵机在 `WaitTO2Lock` 状态达到最终位置。然后,它进入 ARM 状态并再次等待有效的智能卡,以执行相反的操作,激活 R/C 舵机回到初始位置。之后(`WaitTO2UnLock`),它返回到初始的 `IDLE` 状态。

我们将使用调试器来找出舵机(锁定/解锁)的最佳启动和停止位置的 PWM 值。根据您实现锁的机械组件的方式,您将需要不同的 PWM 值来表示两个终端位置。通过使用调试器,我们将更改控制这些位置的两个变量,从而通过试错确定正确的 PWM 值。这个小例子将展示如何使用调试器/监视器的一小部分来开发您的应用程序。

下面显示了一个示例的启动屏幕

boot.png

OS/APP 语句后面的零来自智能卡模块(我的硬件中没有连接读卡器)。

最小和最大 PWM 值存储在两个变量中

v_ServoLock, v_ServoUnLock

我们转到文件 *build.lst/kernel.map*,然后搜索“`v_ServoLock`”

v_ServoLock 0x1 build.obj/applic.o 

0x00800171 v_ServoLock

v_ServoLock build.obj/serapp.o 

build.obj/applic.o

我们找到了三个实例。第一个是变量的大小。第二个是地址(偏移量为 0x800000)。最后一个是使用该变量的模块。

对我们来说重要的是第二个。所以 `v_ServoLock` 的地址是 0x171,它是一个字节。

我们通过在终端输入“* <Enter>”进入串行应用程序。然后我们尝试直接按键操作“n”和“m”来控制舵机的最小-最大值。然后我们通过“& <Enter>”进入调试器。

然后我们读取 `v_ServoLock` 的值

> R 0171
32
> 

返回的值 32 是十六进制,即十进制 50,这是变量的初始值。现在我们把这个值减小到十进制 40 -> 0x28。

> W 0171 28
> R 0171
28
>

下面显示了一个类似的例子

重新进入应用程序,输入“* <Enter>”,然后重试“m”命令。舵机的最大终端位置应该改变!你可以玩转这些,找到可以硬编码到应用程序的最佳值。更好的是,你可以使用 EEPROM 来存储并在程序中用于此类设置。

您可以对 v_ServoUnlock 进行同样的操作,并调整其值。

这是一个小例子,展示了如何无需重新编程控制器即可进行实时测试。您可以测试引脚和端口 I/O,以查看控制器是否“看到”端口上的正确值,或者是否有任何端口似乎浮动(例如,当您触摸引脚时,某个位会随机切换)。

串行应用

串行应用程序是另一个用于直接控制应用程序的示例任务。例如,它与调试器无缝协作。在调试器中输入“*”会恢复到串行应用程序,反之亦然。在串行应用程序中输入“*”可以恢复到调试器。

所示的串行应用程序支持两种不同的模式。直接按键执行和 shell 执行。为简单起见,shell 命令是单个按键(类似于调试器命令),后面可能跟有参数,并以 <Enter> 键(0x0D)终止。

直接按键执行模式是指在终端上按下按键时执行的操作。例如,我们有一些命令可以控制舵机位置,这样您就可以看到您的硬件是否正常工作。例如,按下“c”键,舵机会缓慢地向一个方向旋转,而按下“v”键,舵机会向另一个方向旋转。控制按键操作的函数是“`f_ProcessAPPKey`”。

提供更类似 shell 界面的 shell 函数是“`f_ProcessAPPCMD`”。例如,输入“q <Enter>”与按“c”键效果相同。这样,您的应用程序就可以具有双重控制,并通过终端灵活地进行控制。

一个重要的方面是,进入串行应用程序主函数后,我们会检查调试器位是否已设置。如果已设置,则跳过执行,因为调试器处于活动状态。同时,“`f_ProcessAPPCMD`”必须支持星号“*”命令,以便恢复到调试器。如果您想盲目地跳到调试器模式而不了解之前的状态(使用“*”在调试器和应用程序之间切换),可以使用“&”字符返回到调试器。

结论

在本文中,我提出了一个简单的框架,用于更快、更容易地构建嵌入式系统。当然,开发嵌入式系统还有更简单的方法(例如使用基本解释器),但这是一个非常可扩展的系统,其便捷的开发不会牺牲性能或功能。我从 15 年前开始在 8051 汇编语言中构建这个系统,然后用 AVR 汇编语言编写,之后又用 C 语言重写了整个框架。我随着时间的推移进行了各种修改,现在它就在这里。我向公众提供源代码,以帮助更多人探索嵌入式系统的美妙之处。我也希望人们能贡献自己的外设和扩展,从而建立一个更大的组件库。这将使其他人开发新系统变得更加容易。

许可证

本文,以及任何相关的源代码和文件,均根据 CDDL <https://open-source.org.cn/licenses/cddl1.php> 许可。

历史

  • 版本 1.0,初始发布。

  • 版本 1.1,修正了 *ifc_time.h* 定义(第 77-81 行)中 `INT8U` 到 `INT16U` 的类型值。修正了文章中一些小的错别字。

© . All rights reserved.