EBD Review

基本概念

什么是EBD

定义

嵌入式系统就是将计算机的硬件或软件嵌入其它机、电设备或应用系统中去,构成的一种新系统。
end是以应用为中心,以计算机技术为基础,采用可剪裁软硬件,适用于对功能、可靠性、成本、体积、功耗等有严格要求的专用计算机系统,用于实现对其他设备的控制、监视或管理功能。

区别

  1. 嵌入式系统中运行的任务是专用而确定的
  2. 桌面通用系统需要支持大量的需求多样的应用程序。
  3. 嵌入式系统对实时性有较高要求
  4. 嵌入式:强实时:us-ms,一般:ms-s,弱:s+
  5. 嵌入式中一般使用实时操作系统
  6. 嵌入式需要高可靠性保障
  7. 嵌入式系统需要长时间无人值守条件下的运行
  8. 嵌入式系统有功耗约束
  9. 嵌入式资源少
  10. 嵌入式开发需要专用工具和特殊方法
  11. 嵌入式是综合计算机应用技术

EBD开发的基本过程

并行的特性

嵌入式系统的并行是硬件/软件协同实现的

  1. 始终运行
  2. 必须相应连续和混合的事件
  3. 实时系统对于相应有底线要求
  4. 通常必须并行处理多个独立的任务
  • 设计约束
    • 成本
    • 大小和重量限制
    • 功率和能耗限制
    • 环境

软硬件的划分

决定什么部分用软件实现,什么部分用硬件实现

  1. 硬件和软件具有双重性
  2. 软硬件变动对于系统的决策造成影响
  3. 划分和选择需要考虑多种因素
  4. 硬件和软件的双重性是划分决策的前提

一般软件的部分:

  • 操作系统功能:任务调度,资源管理,设备驱动
  • 协议栈:TCP/IP
  • 应用系统框架:除基本系统,物理接口,基本逻辑电路,许多由硬件实现的功能都可以由软件实现

还有双重性的部分。

通用件

  • 标准构件
    • 已经产品化
    • 形成规模生产
  • 标准构件+自行设计构件=用户系统
  • 构件包括软件和硬件
  • 标准硬构件:
    • IC:集成电路
    • PCB:核心板,接口板
    • IP:intellectual Property
      • 标准IC:CPU,DSP,RAM,ROM,ASIC
      • 标准IP:CPU核
    • 标准模块:GPRS,显示等
    • 标准计算平台:linux板子,安卓板子
  • 标准软构件:
    • OS、RTOS
    • 协议栈:TCP/IP,路由,H.323
    • 图形开发包
    • 驱动程序

MCU

MCUvsCPU

  1. MCU在CPU外带有外设
  2. 至少有定时器和GPIO
  3. 一般还有ADC、UART和PWM等
    CPU:只有指令译码执行,没有外设,如x86
    MCU:带有外设,至少有定时器和GPIO,一般还有UART、AD等,有的还有存储器
    SoC:带有存储器和某个特定外设(如wifi,zigbee)的MCU

选择

intel8051:不再生产,广泛应用于低端消费类电子产品中,价格曾经是优势
Atmel的AVR:8位RISC,Havard架构,片上Flash,Sram,eeprom,CS出身常用
TI的MSP430:16位伪RISC,低功耗,针对仪表市场
Microchip PIC:8位RISC,hazard架构,内存模型复杂
DSP:TI和ADI的产品,支持傅里叶积分,已经在历史尘埃中
ARM:老大哥,第一名 ,分为Cotex-M和cotex-A,有MMU和没MMU

Cortex:

  • Cortex-A:Application,有MMU跑操作系统,主要用于手机平板
  • Cortex-R:real time
  • Cortex-M: Micro-Controller 无MMU,不跑OS或跑RTOS

如何选择stm32:
满足资源要求的最小(封装、容量)型号
每件最终产品上省下的每一分钱都是你的利润
研制阶段采用相同封装下最大容量的型号
BGA?QFN?QFP?DIP?

产品价格组成:
元件物料成本,PCB成本,加工成本,检测成本,结构成本,包装成本

资源不足时:
GPIO数量不够,通过移位寄存器(串并转换)扩展低速GPIO
UART(通用异步收发传输器)、SPI(串行外设接口)、I2C(总线):通过GPIO软件模拟
AD不够:外接I2C或SPI接口的AD芯片,采用CMOs开关矩阵扩展
没有DA:用阶梯电阻通过GPIO实现
定时器不够:软件扩展

性能不足时:
速度不够:改进算法
SRAM不够:改进算法
Flash不够:避免浮点,避免复杂函数,自己写库函数
选择:

  1. 列出需要的接口
  2. 查看软件架构
  3. 选择架构
  4. 确定内存要求
  5. 开始找mc
  6. 看看花费和能耗约束
  7. 查看部件是否可用
  8. 选择development kit
  9. 调查编译器和工具
  10. 实验

ARM

Load/Store架构,三地址指令,每条指令都可以条件执行,能在单周期移位,协处理器指令集可以扩展ARM指令集,包括在编程模式下增加了新的寄存器和数据类型,在Thumb体系结构中使用16位高压缩表示指令集

37个寄存器,31个通用32位,6个状态,每一种处理器模式中:可见15个通用,PC以及1-2个状态寄存器,

ARM工作模式:

  • 模式切换方法:软件控制,外部中断,异常处理
  • 处理器模式:用户,系统,快中断(快速中断信号直接送给ARM内核,无优先级仲裁,优先级最高,还有单独的快中断R8_fiq–R12_fiq),中断(有优先级仲裁),管理,中止,未定义

ISA

条件执行

所有arm指令都可以条件执行,而thumb只有B具有条件执行功能。如果不标明条件代码,默认为无条件执行
0010CS/HS无符号数大于或等于
CC/LO无符号数小于
MI负数
PL正数或0
VS溢出
VC没有溢出
HI无符号数大于
LS无符号数小于或等于
GE有符号数大于或等于
LT有符号数小于
GT有符号数大于
LE有符号数小于或等于
1110AL无条件执行

标志位:Z运算结果为0 C进位标志加法进位,减法借位,移位为移出的最后一位,N负数为1 V
符号位溢出
移位:

ADD R1,R1,R1,LSL #3; SUB R1,R1,R2,LSR R3;

基址加偏址寻址:
LDR R0,[R1,#4] LDR R0,[R1,#4]! 自动会把R1=R1+4

多寄存器寻址:

一次可传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器,多寄存器寻址指令:
LDMIA R1!,{R2-R7,R12}R1指向的存储读出到R2-R7,R1自动加4,叹号回写
STMIA R0!,{R2-R7,R12}保存到R0指向的存储,R0自动加4

SWP指令用于将一个内存单元(地址放在Rn中)的内容读到Rd中,同时将Rm的内容写入到该内存单元。

SWP{cold}{B} Rd,Rm,[Rn]

Rn不能和Rd,Rm相同

函数调用规范

没有call/ret指令,bl指令将当前pc+4存入R14,返回时R14移入pc(R15)就可以返回了
使用栈的规范:满递减类型。
在函数调用之间传递/返回参数:

  1. 4个以下参数:由R0~R3传递
  2. 大于4个时,用堆栈
  3. 返回结果在R0中
  4. R4-R10用于本地变量或临时存储

THumb2兼容16位和32位

ARM用异常表示中断和异常,中断是异常的一部分,表示CPU外部来的异常,这与一般的术语体系相反。
初始化内存:

  • 启动时一个中断,而不是从0开始执行
  • 启动中断是中断向量表的第一项
  • 在0地址的不是中断向量表的第一项
  • 初始堆栈指针由0地址的值指定
地址 内容
0xFFFFFFFF-0xE0 系统层
0xDFFFFFFF-0xA0 外部设备
0x9FFFFFFF-0x60 外部ram
0x5FFFFFFF-0x40 peripherals
0x3FFFFFFF-0x20 SRAM
0x1fffffff-0x00 code

R0-R3不需要保存,用作参数,返回结果和临时变量
R4-R11需要子程序保存和恢复

cortex-m的中断向量:
中断向量表是编译连接时刻确定下来放在flash中的
不能再程序运行中来写入中断处理函数地址。
在启动代码文件中预置中断处理函数。
中断处理函数名要写对。

ICP、ISP、IAP 这三个术语的含义
ISP:In SYSTEM Programing,在系统编程,可以动态编程
ICP:In CIRCUIT Programing,在电路编程,整机烧录
IAP:In application Programing,在应用编程,可以使用接口来下载编程数据

  • 由外部引脚在上电时的电平决定上电后进入用户程序还是片内bootloader
  • boatload可以使用串口、USB、I2C、SPI来和上位机通信

轮询

整个程序就是一个循环,程序按顺序检查每一个需要关心的I/O设备,完成每一件需要完成的工作。

  • 延时通过循环实现,IO操作通过循环等待来确定完成
    • 串口,ADC
      程序逻辑简单直观,不适用中断,系统执行的时间是可预计的,系统执行所需的内存可以预计,没有共享数据冲突的风险。
      缺点:无法对外部事件做出及时响应
  • 如果一个设备需要的最长响应时间小于程序循环一次,无法满足要求

前后台

中断用来处理硬件的紧急请求,并设置相应的操作标识,主循环轮询这些标志,进行后续处理
延时通过定时器中断实现,在大小循环中查看定时标志。

优点:

  • 紧急任务可以放在中断优先处理
  • 大量计算的任务放在主循环,对其他任务的影响被减轻了
  • 任务被分割在中断处理程序和主循环中,代码更加清晰
  • 设备没有中断时不用查询和等待,节省了处理时间

AT指令集是从终端设备(Terminal Equipment,TE)或数据终端设备(Data Terminal Equipment,DTE)向终端适配器(Terminal Adapter,TA)或数据电路终端设备(Data Circuit Terminal Equipment,DCE)发送的。
其对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个字符的长度(包括最后的空字符)。
每个AT命令行中只能包含一条AT指令;对于由终端设备主动向PC端报告的URC指示或者response响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。AT指令以回车作为结尾,响应或上报以回车换行为结尾。

中断驱动

对于低功耗有用,任务分派机制,主函数配置好就去睡眠,只用中断来做正常的程序操作

  • 没有主循环,功耗低
  • 编程模型类似于事件驱动的GUI
  • 通过中断优先级和中断嵌套实现不同任务之间的协调
  • 适用于功能任务简单,任务之间冲突机会小,空闲时间多的产品,不适合任务间可能有冲突的产品

动态执行队列

将处理的各个功能编写成函数的形式
中断产生是,将相应的处理函数的指针放入队列中
主循环不断的读取队列,取出函数指针并调用该函数

优点:

  • 主函数可以按照任何优先级策略来调用队列中的函数
    • 中断函数可以以任何策略来插入函数
  • 高优先级的功能能得到更多的CPU资源
  • 低优先级机会少可能饿死

动态调度

让调度可以按需执行,基于重要程度的调度
非抢占式(任务结束时决定需要运行啥)vs抢占式(任何时候都可以互相抢占)

BootLoader

上电后的第一段代码:

  1. 程序本身:小规模单片机程序
  2. bootloader
  3. BIOS:PC

Boot loader 是在系统启动时在操作系统内核运行之前运行的一段程序。

  1. 初始化硬件设备和建立内存空间的映射图
  2. 将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境
  • 芯片内的ISP程序
    • 不占用地址空间(特殊空间)
    • 二进制协议
    • 只能下载烧录程序
  • 第三方boot loader
    • 在ROM/flash地址空间中
    • 需要由其他方式预先烧录
    • 文本协议
    • 可以做简单调试

嵌入式启动的方式:

  • Flash启动方式
  • 硬盘启动方式
    • 硬盘主引导区放置boot loader
    • 从文件系统中引导操作系统
  • 网络启动方式
    • boot loader放置在EPROM或flash中
    • 通过以太网远程下载操作系统内核和文件系统
    • 开发板不需要配置大的存储介质

简单boot loader:
只有系统引导功能
具有监控功能的boot loader:
调试支持,内存读写,flash烧写,网络下载,环境变量配置。

boot loader阶段:
阶段1:实现依赖于CPU体系结构的代码
阶段2:实现一些复杂的功能

  • 阶段1
    • 硬件设备初始化
      • 屏蔽所有的中断
      • 设置CPU的速度和时钟频率
      • RAM初始化
      • 初始化LED
      • 关闭CPU内部指令/数据Cache
    • 为加载阶段2准备RAM空间
      • 除了阶段2可执行映像的大小外,还必须把堆栈空间也考虑进来
      • 必须确保所安排的地址范围的确是可读写的RAM空间
      • 内存区域有效性方法
        • 保存指定内存区域
        • 写入预订数据
        • 读入数据并比较
    • 拷贝阶段2代码到RAM中
    • 设置堆栈指针SP
    • 跳转到阶段2的C语言入口点
  • 阶段2
    • 初始化本阶段要使用到的硬件设备
      * 初始化至少一个串口,一边和终端用户进行IO输出信息
      * 初始化计时器等
      
      • 检测系统的内存映射
        • 内存映射的描述
        • 内存映射的检测
      • 加载内核映像和根文件系统映像
      • 设置内核的启动参数
      • 调用内核

RTOS

实时系统:指系统能够在限定的响应时间内提供所需水平的服务
嵌入式实时操作系统:VxWorks
Green Hills 公司是世界排名第二的嵌入式操作系统提供商
windows实时化:realtime extension

ucos:优先级抢占式,可移植,可裁剪的多任务实时操作系统。
ThreadX:强实时,内核小,实时性强,高可靠性,源代码开放

任务优先级:静态优先级,动态优先级
因为任务时间片运行完毕而引起的任务调度可以理解为时间片调度,而因为操作系统中最高就绪优先级的变化而引起的调度则为优先级调度。

基于优先级的系统:可抢占性调度是指操作系统可以剥夺正在运行任务的处理器使用权并交给拥有更高优先级的就绪任务,让别的任务运行

基于分时机制的系统:每个任务都能持续占用处理器一段时间,时间用完操作系统就切换任务。

不可抢占型:某任务必须运行完或者主动让出。

共享资源的竞争:访问共享资源,只能有一个同时访问

运行同步:任务间相互协作,按照规定的路线执行,拓扑。

数据通信:任务间的数据传输。

通信:任务间的数据传输,直接数据传输和间接方式

等待机制:(等待IPC(通信))直接返回结果,阻塞等待模式,时限等待模式。

优先级翻转,优先级继承,优先级天花板

中断机制:

  • 外部中断:一般是系统外设
  • 内部中断:处理器自身的原因引发的异常事件,非法指令,总线错误或运算出错。
  • 软件中断:程序通过软件指令触发的,比如为了提升权限进入软件中断系统级

提升内核实时性:可抢占内核,内核关中断时间,存储管理机制(不支持虚拟存储,不支持动态内存分配),任务互斥、同步(资源有限等待,优先级逆转问题解决)

通讯:

  • 共享数据结构,最直接的任务间通信方式,全局变量、现行缓冲区,循环缓冲区,链表,可以被不同上下文环境中运行的代码直接访问,需要互斥方法保护。
  • 消息:内存空间中一段长度可变的缓冲区,任务间,ISR-任务之间的通讯机制,ISR可以写,不能读
  • 常用消息分类:邮箱,消息队列。

同步、互斥:信号量,二元信号量,互斥信号量,计数信号量。

  • 管道:管道是一个虚设备,提供了通过IO设备接口访问消息队列的一个界面,任务可以使用标准的IO接口open,read,write,以及ioctl调用。
  • 时间
  • 信号:

UC/OS

全称:micro Control OS
嵌入式领域研究人员Jean J.Labrosse与1992年在《嵌入式系统编程》杂志的5月,6月刊登上的文件连载,并发布源码。他创立了Micrium公司,提供嵌入式软件和解决方案,出售软件商业许可证
基于优先级抢占式,可移植,可裁剪,多任务实时操作系统。特征为短小精悍。源码用ansi c写的,移植性强。最小可达2kb,最小数据ram要求10kb。ucos可以再8位-64位,超过40中不同架构mcu上运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OS_STK userAppTaskStk1[1000];
OS_STK userAppTaskStk2[1000];

extern void userApp1(void *);

main

OSInit();

OSTaskCreate(userApp1,(void *)0,&userAppTaskStk1[1000-1],5);

OSStart();

OSTimeDly(int);//sleep

OS_ENTER_CRITICAL();
OS_EXIT_CRITICAL();//关中断,进临界区

OSTaskDel(OS_PRIO_SELF);//删除自己,但任务代码还在只是不调用

运行环境:

PC,SP,程序状态字寄存器(PSW),通用寄存器内容,函数调用信息(已存在于堆栈),优先级,状态等,保存在任务控制块TCB中。

OSUnMapTbl[] OSMapTbl[]两张映射表

OSRdyGrp OSRdyTbl[]两个变量

统计任务:OSTaskStat(),每秒计算一次CPU在单位时间内被使用的时间,并把计算结果以百分比的形式存放在变量OSCPUsage中,一遍应用程序通过访问它来了解CPU的利用率。

UCOS:64个优先级别,数字越大优先级越低。

应用程序创建新任务时,需要把初始数据(任务指针,任务堆栈指针,程序状态字等)实现存放在任务的堆栈中。在OSTaskCreate()中调用OSTaskStkInit()来完成任务堆栈初始化工作。

任务就绪表:ucos在内存中设立了一个记录表,系统中的每个任务都在这个表中占据一个位置,使用该位置的状态(1或0)表示是否就绪。

OSCtxSw完成任务上下文切换。

进入中断时,中断嵌套+1,OSIntEnter(),离开时调用OSIntExit()

在中断服务程序中调用的负责任务切换工作的函数是OSIntCtxSw()

时钟节拍(TimeTick),调用OSTimeTick()完成系统每个时钟节拍需要做的工作

同步与通信:

事件控制块(ECB):用来描述信号量,邮箱和消息队列这些时间。事件的总数:OS_MAX_EVENTS,OSEventPtr把空事件都联成一个单向链表,每次创建一个事件块时,系统从链表中取出一个空块,删除事件时归还一个块。

OSSemCreate()创建信号量OSSemPend()请求信号量,可以设置timeout

OSSemPost()释放信号量,先判断有没有等待该信号量的任务,有的话用OS_Sched去调用它,没有的话信号量加1,成功后返回OS_ON_ERR

OSSemDel删除信号量

互斥性信号量:用OSMutexCreate()创建,OSMutexPend()请求,OSMutexPost()发送信号量,OSMutexDel()删除。

消息邮箱:通过传递数据缓冲区指针的方法来通信。OS_EVENT_TYPE_MBOX,OSEVENPTR指向数据缓冲区。

消息队列:在任务间传递多条消息,分为三个部分:事件控制块,消息队列,消息。
信号量还是看ppt吧。
信号量集:有等待任务链表,使用OSFlagCreate()创建信号量集,同样的使用pend和post来请求和发送。

内存动态分配

改进了malloc和free,是执行时间成为确定的。
ucos对内存进行两级管理,大片内存变若干个分区,分区分为内存块,操作系统以分区为单位来管理,任务以内存块为单位来获得和释放。使用情况由内存控制块记录。ucos使用空内存控制块链表管理空内存控制块

移植步骤

要让ucos正常运行,必须:

  • c编译器能产生可重入代码
  • 处理器支持中断,并且能产生定时中断,通常在10-100hz
  • 提供打开和关闭中断的指令
  • 处理器支持能够容纳一定量数据的堆栈
  • 处理器有将堆栈指针,以及寄存器读出,存储到堆栈,或内存中的指令

Linux

ARM有7种运行状态:用户,中断,快中断,监管,中止,无定义,系统

虚拟内存,使用MMU,其余是操作系统VM。

S R CPU特权 CPU用户
0 0 NO Access No
1 0 Readonly N
0 1 R R
1 1 Not sure(可读可写) Not sure(可读可写)

Linux的启动

  1. head.S是linux运行的第一个文件
  2. 内核的入口是text,在arch/arm/kernel/vmlinux.lds.S中定义-ENTRY(text)
  3. vmlinux.lds.S是ld script文件

启动主线:

  • 确定process type
  • 确定machine type
  • 创建页表
  • 调用平台特定的__CPU_FLUSH函数(清除Cache,清除Dcache,清除Writebuffer,清除TLB)
  • 开启mum
  • 切换数据

ARM-linux系统调用

  • LIBC和直接调用
  • arm处理器有自陷指令SWI
  • CPU遇到自陷指令之后,跳转到内核态
  • 操作系统保存当前运行的信息,根据系统调用号查找相应的函数去执行
  • 执行完了返回,恢复信息。

arch/arm/kernel下写.c系统函数
arch/arm/kernel/call.S中添加新的系统调用调用号和函数名

设备驱动程序的重要性

设备驱动程序往往工作与系统内核状态,因而运行性能,可靠性制约着应用系统的性能和可靠性

Linux的设备驱动

由接口充当应用程序和实际硬件之间的桥梁。

设备驱动程序:
是直接控制设备操作的那部分程序,是设备上层的一个软件接口。
实际上对于软件角度来说,设备驱动程序就是负责完成对IO端口地址进行读写操作。
设备驱动程序的功能是对对IO进行操作,且只能被调用。
与操作系统内核的接口:
与系统引导的接口:完成对设备的初始化。
与设备的接口,完成对设备的交互操作功能
与操作系统内核的接口:include/linxu/fs.h中的file_operations数据结构
与系统引导的接口:字符设备初始化在drivers/char/mem.c中的chr_dev_init()函数,块设备初始化在drivers/block/II_rw_blk.c中的blk_dev_init()函数
与设备的接口:完成对设备的交互操作功能

外部设备:

  1. 字符设备(像字节流一样被访问的设备,不允许来回读写,以字符为单位传输,可以通过文件的方式访问)
  2. 块设备(以数据“块”为单位)
  3. 网络设备(能和其他主机进行网络同学的设备,基于BSD套接口访问)

设备驱动程序表:描述了系统中所有设备驱动程序,以及设备驱动程序所支持的操作的入口地址。

当应用程序打开/dev下的文件,并进行各种操作时,OS将应用程序的调用及参数转给注册过的对应的对应的设备驱动程序的对应函数。

对设备文件进行系统调用时,将从用户态进入到内核态。内核根据该设备文件的设备类型和主设备查询相应的设备驱动程序,有驱动程序判断设备号,完成相应的操作。

接口与外设

GPIO就是可以由CPU直接操纵的引脚,可以写,可以读,可以接通信单元。


如果要使用GPIO那么首先要include,并注意在gpio的使用上使用HIGH和LOW来代表输出的信号,而不是直接使用1或0(因为在不同的平台上对HIGH和LOW的定义可能会不一样)。
对于测试GPIO引脚的数字是否可用,需要使用

1
int gpio_is_valid(int number);

对于申请GPIO要使用:

1
int gpio_request(unsigned gpio,const char* label);

使用完之后释放GPIO需要:

1
void gpio_free(unsigned gpio);

对于激活GPIO为输入或输出,需要使用以下的两个函数:

1
2
int gpio_direction_input(unsigned gpio);//初始化为输入
int gpio_direction_output(unsigned gpio,int value)//初始化为输出

返回值为0为成功,负值为错误。对于初始化为输出,value则是初始输出值。

读取值或者设置输出值使用:

1
2
int gpio_get_value(unsigned gpio);//读取值
void gpio_set_value(unsigned gpio,int value)//设置输出值

返回值1为HIGH,0为LOW。这两个函数没有错误返回,因为在一开始错误的引脚就不能被初始化。

对于使用I2C或者SPI这种方式的设备来说,需要等待对方的起始信号。使用

1
int gpio_cansleep(unsigned gpio);

随后使用:

1
2
3
//返回值为0-low,1-high,可能会进入等待
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio,int value);

来读取引脚或设置引脚电平。

将GPIO映射为IRQ中断:

1
2
3
4
//将GPIO映射到对应的IRQ编号
int gpio_to_irq(unsigned gpio);
//IRQ->GPIO
int irq_to_gpio(unsigned irq);

/sys的局限(文件用于直接修改gpio)

这个级别的程序会受到两种额外的延时和延迟的影响。一种是调度延迟,即切换的时间,一种是系统负载,其他更重要的应用程序需要使用处理器,系统不会让你的程序使用处理器。

linux访问GPIO

内核级:使用设备驱动程序等
虚拟文件系统级:
应用程序:
让内存映射到AHB上,访问/dev/这种???(不确定)

通信

异步通信是指通信的发送和接收设备使用各自的时钟控制发送和接受。
接口信号
DST数据装置准备好,表明通信装置可用。DTR数据终端可以使用。
接收线信号检出(RLSD),用来告知DTE准备接受数据。振铃提示(RI),通知终端已经被呼叫。

RS232
TxD和RxD上:
逻辑1(MARK)=-3V~-15V
逻辑0(SPACE)=+3V~+15V
RTS、CTS、DSR、DTR和DCD等控制线上:
信号有效(接通,ON状态,正电压):+3V~+15V
信号无效(断开,OFF状态,负电压):-3~-15

SPI

  • Serial Peripherals Interface
  • 应用于外部移位寄存器,D/A,A/D,等外部设备进行扩展
  • 接线:
    • MOSI-SPI从输入,主输出
    • MISO-SPI主输入,从输出
    • SPICLK-SPI时钟
    • SPICE-SPI从发送使能

SPI的优点

  • Fast and easy
  • Everyone supports it

Multiple receivers do not require separate select lines
as in SPI

  • At start of each I2C transaction a 7-bit device address
    is sent
  • Each device listens – if device address matches internal
    address, then device responds
  • SDA (data line) is bidirectional, communication is half
    duplex
  • SDA, SCLK are open-drain, require external pullups
  • Allows multiple bus masters
1
2
3
4
5
6
7
8
9
10
11
Speed Comparison
• Assume a 400 Khz I2C bus, 2.5 us clock period
(2.5 e-6)
• Random write:
– 9 bit transmission = 2.5 us * 9 = 22.5 us
– 5 ms + 22.5 us* 4 (control,addhi,addlo,data) =5.09 ms
– For 64 bytes = 325 ms approximately, not counting software
overhead.
• Page Write
– 67 bytes total (control, addhi, addlo, data)
– 5 ms + 67 * 22.5 us = 6.5 ms!!!

文件系统

  • ROM文件系统
    • ROMFS是一种只读文件系统
  • 磁盘文件系统
    • FAT16,FAT32,Ext2FS,NTFS
  • Flash文件系统
    • TrueFFS、MFFS
    • JFFS/JFFS2,YAFFS/YAFFS2
    • FAT16,FAT32,NTFS,exFAT
  • 内存文件系统

NORvsNAND

  • NOR型闪存的特点:
    • 具有独立的地址线,数据线,支持快速随机访问,容量较小
    • 具有芯片内执行(execute in place)的功能,按照字节为单位进行随机写
    • NOR适合用来存储少量的可执行代码
  • NAND:
    • 地址线、数据线公用,单元尺寸比NOR小,更高的价格容量比,高存储密度和大容量
    • 读写采用512字节的页面
    • NAND更适合作为高密度数据存储

写前需要擦除,擦除单元(block)> 读写单元(Page):

文件系统分类

  • 硬盘模拟法:将闪存设备模拟成具有每个扇区512字节的标准块设备,在此基础上用成熟的磁盘文件系统进行管理。
    1. 读入整个擦除快,修改,重写整个块
    2. 不考虑擦除的均衡性,某些块可能很快被写坏
    3. 系统一致性没有安全保证,内存可能随时断电,这种丢失无法恢复
    4. 为了提供均衡性和可靠的操作, 模拟块设备的山区存放在物理介质的不同位置上,地址转化用于记录当前每个扇区在模拟块设备上的位置。
  • 系统实现层次:
    • 设备驱动程序层
    • 地址转化层
    • 文件系统管理层
  • TrueFFS Flash模拟硬盘程序包,M-system公司

    • 动态和静态的损耗级别判定算法
    • 安全算法保证在突然断电时数据完整性
    • 解决为交换问题的Reed-Solomon纠错算法
    • 自动的坏块管理
    • 优化处理功能,减少擦除次数,优化垃圾回收操作
    • 容量*总擦写次数*0.75/每天的写入字节
  • 直接实现法:直接对闪存设备进行操作,建立日志文件系统,避免模拟转化工作

    • 去掉FTL这一层,对性能有很大提高
    • 日志文件系统:特点是对数据的更新采用前向写入。更新的数据写入空白快。

uC/OS室温计

#5 uC/OS室温计

这个实验的目的是理解uC/OS II的任务调度方式,编写uC/OS II的应用程序,通过寄存器直接操纵GPIO来驱动外 部设备。

##配合课程

RTOS

##实验目的

学习uC/OS II的应用程序编写;
理解如何直接操纵GPIO,体会与Linux的不同;
学习单总线设备的访问方式;
学习7段数码管的时分复用驱动方式。

##实验器材

###硬件

STM32F103板一块;
USB串口板一块;
面包板一块;
两位7段数码管(共阳)一颗;
360Ω 1/8W电阻2颗;
DHT-11 温湿度传感器1个;
面包线若干。

###软件

####编译软件;
Fritzing。
uvison5.

#实验步骤

设计输出方案,画连线示意图;
在面包板上连线,完成外部电路;
编写C/C++程序,测试程序和电路;

  1. 测试、实现uC/OS II对GPIO的访问;
  2. 实现DHT-11数据的读;
  3. 实现以时分复用方式在四位7段数码管上依次显示0000-9999的数字;
  4. 用两个uc/OS II任务,一个定时读DHT-11数据,一个轮流驱动数码管,一秒一次显示当前温度和湿度。注意处理好 两个任务之间的数据共享。

#连线示意图
segment

seg

以上为四位共阳数码管的引脚图片
seg

以上为四位共阳数码管的接线图片,由于在fritzing中没有找到合适的stm32板子,所以将这块25pin的引脚除去下面的6个pin就当它做STM32F103了,在实体连线中,将数码管COM1-4分别连接到了C15-14,A0-A1,其余脚按照顺时针顺序连接到了STM32板的两端。
dht
如图为dht的连线图,在本次试验中,Vcc接3.3V,Dout接A8,GND接地,为了读数稳定,需要在地线和Dout间连接一个上拉电阻。

#实验过程

###UCOS的移植

在UCOS移植的过程中参考了网上的资料。
IDE环境:uvision5, UCOS源码
文章来源sina blog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
1.新建ucos工程,选择STM32F103VE,选择CMSIS下的CORE和Device下的Startup,以及Device下的StdPeriph Drivers下的Framework,RCC,和GPIO
2.工程中和实际目录中都新建几个目录,APP,UCOS,BSP,LIB,CPU,Output
3.工程上右键,Options,Output页签,Select Foldeer for Objects,进入Output目
录,点击OK
4.把Micrium\Software\uCOS-II\Source目录中的文件拷贝到UCOS目录下,并添加到工程

5.工程Options中,C/C++页签,Include Paths,点击后面省略号可选择include目录,添
加UCOS路径
6.复制Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\OS-Probe目录下
的文件app_cfg.h,os_cfg.h和includes.h到APP目录中,并在Include Paths中添加
APP
7.复制Micrium\Software\uCOS-II\Ports\arm-cortex-m3\Generic\RealView目录
下的所有文件到CPU目录,添加到工程和Include Path中
8.工程Options中,C/C++页签,Defines中添加 USE_STDPERIPH_DRIVER
9.把RTE和RTE\Device\STM32F103VE添加进Include Paths中
10.修改os_cfg.h文件,#define OS_APP_HOOKS_EN 1为0
11.BSP目录下新建BSP.c文件,添加内容如下:
#include

CPU_INT32U BSP_CPU_ClkFreq (void)
{
RCC_ClocksTypeDef rcc_clocks;

RCC_GetClocksFreq(&rcc_clocks);

return ((CPU_INT32U)rcc_clocks.HCLK_Frequency);
}

INT32U OS_CPU_SysTickClkFreq (void)
{
INT32U freq;

freq = BSP_CPU_ClkFreq();
return (freq);
}
12.复制Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\BSP目录下的bsp.h到 BSP目录中
13.复制Micrium\Software\uC-CPU\ARM-Cortex-M3\RealView目录和Micrium\Software\uC-CPU目录下的所有文件到CPU目录下,并添加到工程和Include Path中
14.复制Micrium\Software\uC-LIB目录下的所有.h文件到LIB目录下,并添加到Include
Path中
15.注释掉bsp.h中的#include 和#include
16.app_cfg.h文件中,修改为#define APP_OS_PROBE_EN 0
17.APP目录下新建app.c文件,内容为
#include

int main(){
OSInit();
OSStart();
return 0;
}

18.注释掉includes.h文件中的#include 和#include

在app.c中还需要添加如下时钟中断处理函数:

1
2
3
4
5
6
7
8
9
10
void SysTick_handler(void){
OS_CPU_SR cpu_sr;

OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();

OSTimeTick();
OSIntExit();
}

这样全部做完之后代码结构如图:
代码结构

###GPIO的访问
首先,在GPIO初始化函数中,初始化所有我们需要的GPIO,当然这里是全部要用到的都初始化了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void GPIO_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure;

RCC_DeInit();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA, ENABLE);


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14
| GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11
| GPIO_Pin_12;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0
| GPIO_Pin_1
| GPIO_Pin_2
| GPIO_Pin_3
| GPIO_Pin_4
| GPIO_Pin_5
| GPIO_Pin_6
| GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}

随便点个小灯来测试一下吧(其实是做完后面的反过来做得这个):

1
GPIO_WriteBit(GPIO_C,GPIO_Pin_14,RESET);

###时分复用的八位共阳数码管
首先为了在单独的一位数字上显示出我们想要的数字,要根据数码管每一位所对应的引脚输入正确的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned char table[10][8] =
{
{0, 0, 1, 1, 1, 1, 1, 1}, //0
{0, 0, 0, 0, 0, 1, 1, 0}, //1
{0, 1, 0, 1, 1, 0, 1, 1}, //2
{0, 1, 0, 0, 1, 1, 1, 1}, //3
{0, 1, 1, 0, 0, 1, 1, 0}, //4
{0, 1, 1, 0, 1, 1, 0, 1}, //5
{0, 1, 1, 1, 1, 1, 0, 1}, //6
{0, 0, 0, 0, 0, 1, 1, 1}, //7
{0, 1, 1, 1, 1, 1, 1, 1}, //8
{0, 1, 1, 0, 1, 1, 1, 1} //9
};

以上的代码分别对应了0-9的数字在数码管上的显示IO管脚电平,按照顺时针方向,从8字顶端开始为ABCDEFG,DP,而代码中从高到低对应DP,GFEDCBA。这样就可以在数码管上显示单个数字了,为了保险起见,可以给数码管接个电阻避免烧坏。

为了同时点亮四个数字,利用人眼的余晖效应,只要快速切换点亮每个数字,在人眼看来就像是同时亮起来的一样。这时就需要时分复用技术了。对于共阳数码管,每个数字当我们想让他亮的时候,将对应的阳极置为高电平即可,如下为选择数码管位数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void digit_choose(int index){
BitAction v[4];
int i;
for (i=0; i<4; i++){
if (index == i){
v[i] = Bit_SET;
}else{
v[i] = Bit_RESET;
}
}
GPIO_WriteBit(GPIOA, GPIO_Pin_11, v[0]);
GPIO_WriteBit(GPIOA, GPIO_Pin_12, v[1]);
GPIO_WriteBit(GPIOC, GPIO_Pin_14, v[2]);
GPIO_WriteBit(GPIOC, GPIO_Pin_15, v[3]);
}

再在主程序中编写时分复用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void display(int digit){
static int index=-1;
int i;
int base=1000;
index=(index+1)%4;
for (i=0;i<index;i++){
base/=10;
}
digit=(digit/base)%10;
digit_choose(index);//选择数位
digit_output(digit,0);//利用定义好的管脚参数显示一个数字
}
...
main(){
...
count=0;
while(1)
{
count++;
for (int i=0;i<4;i++){
display(count/10);//为了让数字增长不过快
Delay_ms(5);
}
}
...
}

这样就可以在数码管上显示数字0-9999了。

###DHT11温湿计使用

DHT11是一种单总线的传感器,这意味着它的输入和输出引脚是公用的。这样就需要以精确的时间给它发送信号,它的开启模式是这样的:
dht

1
2
3
4
总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证
DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送
80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后,读取DHT11的响应信号,
主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高。

DHT11的响应信号为将电平拉高,通常拉高80μs后就会开始传输数据。数据格式如下:

1
2
3
4
5
6
数据格式:8bit湿度整数数据
+8bit湿度小数数据
+8bi温度整数数据
+8bit温度小数数据
+8bit校验和 数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温
度整数数据+8bit温度小数数据”所得结果的末8位。

这样就可以在GPIO A8口读到DHT11送来的数据了。
在程序中我们需要编写将DHT11激活的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void DHT11_Set(int state){//设置数据读取引脚电平
BitAction s;
if (state){
s = Bit_SET;
}else{
s = Bit_RESET;
}
GPIO_WriteBit(GPIOA, GPIO_Pin_8, s);
}

void DHT11_Pin_OUT(){//将数据引脚设置为主机输出并拉高
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DHT11_Set(1);
}

uint8_t DHT11_Check(){
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8);
}

void DHT11_Wait(int state){
while(DHT11_Check()!=state){

}
}

void DHT11_Pin_IN(){//将数据引脚设置为输入并拉高
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DHT11_Set(1);
}

void DHT11_Rst(){
DHT11_Pin_OUT();//置为输出
DHT11_Set(0);//拉低给初始信号
Delay_us(25000);//延迟大于18ms
DHT11_Set(1);//拉高
Delay_us(30);
DHT11_Set(0);//转为输入,等待DHT11的信号
DHT11_Pin_IN();
}
...
main()
{
...
DHT11_Rst();
if (DHT11_Check()==0){
DHT11_Wait(1);
DHT11_Wait(0);//DHT拉低准备输出
...
}
...
}
...

激活后再按照传入的数据格式编写一下读取数据的函数即可读取DHT11传来的数据了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
uint8_t DHT11_Read_Byte(){
int i, cnt;
uint8_t data = 0;
for (i=0; i<8; i++){
cnt = 0;
data <<= 1;//每次读一位数据
DHT11_Wait(1);
while (DHT11_Check() > 0){//多次读取
Delay_us(1);
cnt++;
}
data |= cnt > 5;
}
return data;
}

uint8_t DHT11_Read_Data(uint8_t *buf){
int i;
int cpu_sr;
OS_ENTER_CRITICAL();//进入critical区,避免抢占
DHT11_Rst();
if (DHT11_Check() == 0){
DHT11_Wait(1);
DHT11_Wait(0);
for (i=0; i<5; i++){
buf[i] = DHT11_Read_Byte();
}
DHT11_Pin_OUT();//重置数据引脚
OS_EXIT_CRITICAL();
if (buf[0] + buf[1] + buf[2] + buf[3] == buf[4]){//检验校验和
return 1;
}else{
return 0;
}
}else{
OS_EXIT_CRITICAL();
return 0;
}
}

至此已经可以读取DHT11的数据了。

###双任务执行

使用UCOS来执行两个任务其实非常方便,只需要使用UCOS开启两个进程即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void gainData(void * data)
{
uint8_t buf[5];
memset(buf,0,sizeof(buf));
while(1){
if (DHT11_Read_Data(buf)==1)
{
if (flag==0)
ledValue=buf[2];//湿度
else
ledValue=buf[0];//温度
flag=!flag;
}
Delay_ms(1000);
}
}
void ToSegment(void * data)
{
while(1){
display(ledValue);
Delay_ms(5);
}
}
#define size 100
int STK1[size],STK2[size];
int main()
{
...
OSInit();
OS_CPU_SysTickInit();
OSTaskCreate(gainData,(void*)0,(OS_STK *)&STK1,1);
OSTaskCreate(ToSegment,(void*)0,(OS_STK *),&STK2,2);
OSStart();
...
}

运行结果为:


How to flash an Acadia

Flash an Acadia

Sometimes the system on the Acadia might break down and it is impossible for user to reach the system in the Acadia. The best way to solve this problem is obviously flashing a new system into the Acadia.

According to the Tutorial on Flashing LinkSprite Acadia written by the producer of Acadia, there are two methods to rescue broken system on Acadia.

Note

This article is focused on Acadia, if you are trying to flash another version of pcDuino, please find information on learn.linksprite.com

How to flash

  1. Download the relevant files Mfgtools-Rel-4.1.0_Acadia_MX6Q_UPDATER.zip(remember to choose the Release version using Mfgtools).
  2. After extract all files, the directory is shown as below:
    pic
  3. Now if you want to flash the system into the eMMC on Acadia, change the [LIST]->name of cfg.ini like this:
    name=Acadia-eMMC
    And if you want to flash the system into the SD card,just change the content as
    name=Acadia-SD1, which represents the SD slot in the front of Acadia.
    front sd
    name=Acadia-SD2, which represents the SD slot in the back of Acadia.
    Remember to restart the Mfgtools after you change the ini file.
  4. Now launch the Mfgtools, and connect the Acadia with your PC using its OTG USB port.
  5. Make sure the boot switches are all in OFF position.
    pic
  6. Now click Start in Mfgtools and wait until it finishes.
    mfgtool

Start Acadia

After flashing, the Acadia can start from eMMC, SD1 or SD2. It depends on the boot switches.

Start From S1 S2 S3 S4 S5 S6 S7 S8
eMMC ON ON Off ON Off ON ON Off
SD1 Off ON Off Off Off Off ON Off
SD2 ON Off Off Off Off Off ON Off

Bicycle Cpu

自行车码表CPU选择


码表CPU需要承担的功能

  • 连接自行车轮圈上的感应器,记录轮圈速度
  • 在液晶屏上显示当前速度
  • 开关功能

对CPU的要求

  • 可以即时处理感应器发来的信息,在1s内更新自行车速度,否则用户体验会很差
  • 由于使用小型锂电池供电,能耗不能过高
  • 提供足够感应器和液晶屏显示需要使用的引脚数量
    • 感应器一般使用两个引脚
    • 液晶屏是八位共阴或共阳数码管,只需要大约5-8个引脚即可使用
  • 需要提供浮点运算功能,计算当前速度
  • 成本不能太高
  • 在STM32家族中选择

CPU的选择

  • CPU主频
    由于1s内只用做极少次数的数学运算,同时对速度更新的要求相对来说比较宽松,可以选择低频的CPU,在STEM32家族中,选择24MHz主频的系列极为合适
  • Flash闪存
    在自行车码数的计算中实在不需要什么存储,16KB已经完全足够用来存储需要使用到的程序了
  • RAM
    在自行车码数的计算中可能会要求存储到之前几秒的感应器数据,所以4K的RAM也足够实用了
  • 引脚数量
    任意一款STM32的核心都可以担当自行车码表的重任

    综上所属,选择STM32F100C4作为自行车码表的CPU是可行的,其价格也非常可人,在大量购买时甚至可以以低于8CNY的价格买到。

GPS on your hands

GPS手持地图终端


现今智能手机虽然几乎霸占了整个导航市场,但是在很多情况下,GPS手持地图终端还是有需求量的,例如在特种情况(如测量,探险,军事)下的使用,这给予了手持地图终端存在的合理性和必要性。市场上也有相当一部分的导航仪,如 摩托车导航仪

功能性能描述

应用场景

  • 适用于自行车装配或手持
  • 可以在户外环境下保持长期正常运行,防水防尘等
  • 具有简便的交互,发生错误可以快速复位

基础功能

  • 离线地图上实时的导航和定位功能,包括
    • 显示地图,以及地图的读取和保存,将地图下载到终端的功能
    • 实时根据GPS信息在地图上显示出相应的位置和方向,经纬度等信息,帮助使用者了解自己的位置,这个功能的响应延迟要求小于1秒
    • 使用者可以输入自己想要去到的目的地,终端根据使用者的位置计算出到达目的地的路径,并在终端上显示出来
  • 终端自身功能
    • 开关机,调节显示屏亮度,显示剩余电量和GPS连接信息
    • 调节显示地图的比例尺
  • 硬件功能和要求
    • 可以稳定安放在自行车把手上的固定器
    • 防水功能
    • 可供输入的按钮
    • 可连续使用4小时以上的续航
    • 5寸的显示LCD屏

附加功能

  • 手电筒功能
  • 移动速度提醒

硬件和软件实现

终端所需软件

  • ARM安卓操作系统,只会自动运行地图程序
  • 地图程序
    • 一个用于显示地图和其余信息的GUI
    • GPS模块信息处理功能,解析GPS模块传来的信息并存储,供之后使用
    • 地图显示功能,解析存储在rom中的地图数据,并根据当前GPS的经纬度信息和比例尺显示对应的地图
    • 可以接受目的地信息,并实时根据当前所在位置计算更新到达目的地的路径
    • 可以接受外部端口发送的信息,提供目的地设定和比例尺缩放功能

终端所需基础硬件

  • 安卓开发核心板一块
  • GPS模块一个
  • 符合供电要求的可充电锂电池
  • 5寸LCD显示屏一个
  • 塑料外壳
  • 5寸触摸屏一块
  • 16G sd卡一张
  • 手电灯泡一个

地图功能实现

  • 离线地图功能
    • 系统开机自动运行地图程序,并每隔一段时间检查地图软件是否关闭,如果关闭则打开地图
    • 地图以程序能读取的方式存储在sd卡中,同时由配套的pc端软件下载地图到sd卡中
    • 地图读取GPS信息,调取相应的地图显示在LCD屏中
    • 通过触摸屏上的对应功能来使用相应的功能
      1. 设定目的地,开始导航
      2. 取消导航
      3. 放大或缩小地图
      4. 刷新定位
  • 导航功能
    • 利用现成算法计算使用者与目标点之间的合适路径
    • 将路径用不同的方式显示在地图上

成本估计

  • 开发板 100-150
  • LCD屏 40-
  • 触摸屏 40-
  • GPS模块 70~
  • 其余杂项 30~