首页资源分类嵌入式系统 > 一天攻破K60&KL26

一天攻破K60&KL26

已有 445464个资源

下载专区

上传者其他资源

    文档信息举报收藏

    标    签:kl26

    分    享:

    文档简介

    关于kl26的一些资料

    文档预览

    2014 攻城略地—— 一天攻破 K60/KL26 山外 K60/KL26 应用手册 最适合初学者入门飞思卡尔单片机的教程 freescale 作者:山外メ雲ジ 广州松飞电子科技有限公司 2014-12-13 攻城略地之一天攻破 K60/KL26 —— 松飞科技 前言 山外 想快速上手 K60/KL26 单片机吗?山外 K60/KL26 库,是你最好的选择:简单的 调用函数接口,良好的编程风格,让你可以不了解寄存器配置的情况下快速入门 Kinetis 系列单片机。 目前的单片机种类越来越多,仅仅飞思卡尔的 Kinetis 系列单片机就有 8 大系列, 每个系列还有多个子系列,例如 K 系列里有 K1x、K2x、K3x、…、K7x 等多个子系列。 K6x 子系列里还可以继续细分为 K60DN、K60FX、K64FX 等等… 目前的单片机型号实在太多了,单片机功能越来越强大,寄存器越来越复杂,假 如每使用一款单片机都要我们从头开始开发,那得开发到何年何月? 山外针对比赛常用的 K60/KL26 单片机,开发出相应的底层驱动库,从而方便应 用者专注于开发自己的应用程序,而不是为了开发底层而占用过多的时间。 山外 K60、KL26 库,尽可能地把底层的驱动完善,用户可以直接调用 API 接口, 而不必慢慢对着 datasheet 来研究。例如 UART、FTM、I2C 等模块,函数内部会根据 系统时钟频率来自动计算和选择分频系数,用户不必担心更改频率后模块不能使用。 当然,如果 main 函数里中途更改时钟频率,就需要重新初始化,以便重新计算和选 择分频系数。 山外论坛 http://vcan123.com 山外メ雲ジ ~1~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 有的人说,用库,就好比填空那样,傻瓜式地写代码,学不到任何的东西。这个 要分情况来讨论: 情况一:初学者,从没系统学过一款单片机,想深入研究底层的开发。 对于这种情况,初学者应该重点放在于研究底层是怎么实现的,参考优秀的底层 库思想,为自己所用。 我们推荐的是参考我们的书籍:《轻松玩转 ARM Cortex-M4 微控制器-基于 Kinetis K60》,这是一本适合初学者进阶的书籍。本书针对初学者对 C 语言、时序 图、Datasheet 不熟悉,重点讲解这方面的内容。从山外自身经验来看,基础知识的 缺乏是影响学习进度的主要原因。只要基础扎实,那么任意来一款单片机, Datasheet+官方例程,基本上可以快速上手,甚至写成自己的库。 (注:书上注明技术支持论坛是野火初学论坛,但由于本书作者(山外メ雲ジ) 已经从野火公司独立出来,野火论坛不再做任何飞思卡尔产品的技术支持,相应的技 术支持论坛转到【山外论坛】:http://www.vcan123.com ) 例如下图就是 PORT 模块寄存器的内存分布: 山外论坛 http://vcan123.com 山外メ雲ジ ~2~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 情况二:初学者,从没系统学过一款单片机,但想快速用上一款单片机。 山外 面对这种初学者,我们的建议是需要加固基础知识,强化 C 语言、时序等知识, 在这基础上才使用我们的库,否则遇到程序 bug 时就没法自行解决。 只要在良好的基础上,才可以任意上手不同的库,从而快速开发自己的产品。 情况三:有基础者,没时间研究底层,想快速用上一款单片机。 对于有基础的学者来说,一通百通,直接在现有的库基础上拓展自己的应用,专 注于自己的应用开发,从而减少开发时间。 目前的应用需要越来越多,越来越难,利用现有的库,可以大大降低开发时间和 开发难度。其他常见的库有:FATFS、ucos、emwin 等等。 为了让初学者快速入门,减少学习时间,尤其是为了那些参加智能车比赛而没时 间学习深入研究 Kinetis 单片机朋友,我们特意写了 Kinetis 开发板的教程。力求大大 减少初学者的学习时间。 山外 Kinetis 开发教程,主要有 IAR 的使用教程、Kinetis 启动流程讲解、山外 K60 库的调用 三个部分组成。我们不再详细讲解寄存器(需要详细讲解寄存器配置 可参考我们的书),而且推荐你们直接调用我们的函数库。山外 K60 函数库,函数内 部会自动计算频率,设置分频,直接调用,减少你们的后顾之忧,可以加快你们的开 发速度。 如果是初学者,想深入学习 K60 的寄存器配置,想自行编写自己的库,可参考山 外编写的 K60 书籍: 发售啦!!!《轻松玩转 ARM Cortex-M4 微控制器-基于 Kinetis K60》 - 全部帖 子 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=6300&extra=page%3D1 想快速上手 K60 单片机吗?山外 Kinetis K60 库,是你最好的选择!!! 我们的口号是:一天 攻破 K60/KL26! 山外论坛 http://vcan123.com 山外メ雲ジ ~3~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 只要我们敢拼,一切皆有可能!!!Nothing is impossible !!! 由于个人能力及时间所限,出错之处,在所难免,欢迎各位指出错误及提出建议: minimcu@foxmail.com ——山外メ雲ジ 广州松飞电子科技有限公司 淘宝店:http://shop112084796.taobao.com 山外论坛:http://vcan123.com 2014-12-11 附带一份 K60/KL26 学习策略: 学习 K60/KL26 就像玩游戏一样,教程就是攻城策略,先有良好的基础,再来攻 破 K60/KL26…… 山外论坛 http://vcan123.com 山外メ雲ジ ~4~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 例如 K60/KL26 的 PORT 管脚管理模块,他就好比城门兵,管理端口的复用和配 置属性。 GPIO 就好比步兵那样,简单易用,可模拟时序,如同步兵可以学习特殊技能来 升级为其他特殊兵种。 UART 串口通信模块、I2C 通信模块,SPI 通信模块,就是按照特点的时序,进行 通信,就好比通信兵团,烽火兵团那样,按照预先设定的规则进行通信。例如古代战 争,看到山上起火啦,战士们就冲啊,杀啊…… 成吉思汗当年带领十万大军攻打宋朝的时候,说:冲啊,杀啊。为什么十万大军 纹丝不动?…… 因为成吉思汗当时说的是汉语,那十万蒙古兵听不懂汉语。呵呵,说个笑话来形 容下通信规则的重要性。 如果不按照特定的时序进行通信,对方就会听不懂你说什么,就没法正常工作了。 ADC、DAC,其实就是模数转换和数模转换,自然界存在的物理量都是模拟量, 单片机作为数字系统,测量读取外部信息时,经常都是需要进行模数转换,而反馈给 外部的时候,如果外部需要模拟量,就可能要进行模数转换了。ADC 和 DAC 就好比 战争中的侦查间谍兵那样,进行情报侦查,信息发布等等。 古代战争,一般用漏斗和圭表等来计时,K60 同样也有自己的计数器,例如 PIT 计数器、LPTMR 计数器、FTM 计数器和 Cortex-M4 自带的 SysTick 滴答定时器。这些 模块都是用作计时延时……,另外 FTM 模块还有输出 PWM 的用途。 战略物质,非常重要,古代战阵也需要专人来押运,K60 在传输大容量数据的时 候,就可以让 DMA 模块来完成,DMA 模块可以不需要 CPU 干预的情况下,按照事先 设定的规则,例如源地址,目的地址,每次传输多少个字节,共传输多少字节,什么 条件下传输来达到运输数据的目的。 山外论坛 http://vcan123.com 山外メ雲ジ ~5~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 有战略物资,就自然需要存储的地方,K60 提供 Flash 存储和 SD 卡存储等功能。 另外,除了前面讲的通信兵团外,K60 还有一些特殊的通信兵, 例如 FlexBus,通信骑兵,特定就是灵活快速地通信,可用在外部 RAM 和 ROM, TFT 液晶等读写数据。 还有 CAN,差分传输数据,具有良好的抗干扰通信能力,多个 can 设备共用一条 CAN 总线 还有 USB,这个就是司令部通信员了,他可以跟电脑通信,电脑就是相当于司令 部,我们当然就是司令员啦,指挥 K60 这个军团来攻城略地!…… 山外论坛 http://vcan123.com 山外メ雲ジ ~6~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 目录 山外 前言 ................................................................................................................................................................................... 1 目录 .....................................................................................................................................................................................7 Kinetis 介绍................................................................................................................................................................... 10 Kinetis 的启动分析(初学者大概浏览一下即可) ............................................................................................ 13 初步入门:初始化函数启动执行顺序 .................................................................................................................................................... 13 逐步提高:ROM、RAM 启动工作原理、ICF 文件讲解 ................................................................................................................. 14 IAR 的使用 ..................................................................................................................................................................... 24 安装 IAR................................................................................................................................................................................................................ 24 建立 IAR 工程 .................................................................................................................................................................................................... 26 创建工程文件 .............................................................................................................................................................................................. 26 IAR 工程选项配置..................................................................................................................................................................................... 35 IAR 使用教程...................................................................................................................................................................................................... 49 工具栏功能介绍 ......................................................................................................................................................................................... 50 通过 jlink 下载并调试 ............................................................................................................................................................................. 52 IAR 界面风格设计..................................................................................................................................................................................... 56 山外 K60/KL26 库的使用 ......................................................................................................................................... 58 快速开发指南 ..................................................................................................................................................................................................... 58 BUG 更新汇总 ............................................................................................................................................................................................. 58 快速入门:了解山外 K60/KL26 工程............................................................................................................................................. 58 安全检查........................................................................................................................................................................................................ 68 PORT 模块 ........................................................................................................................................................................................................... 71 快速入门:PORT 库使用方法 ............................................................................................................................................................. 71 PORT 测试例程 .......................................................................................................................................................................................... 76 GPIO 模块 ............................................................................................................................................................................................................ 81 快速入门:GPIO 库使用方法 .............................................................................................................................................................. 81 GPIO 测试例程............................................................................................................................................................................................ 93 VCAN_LED 模块 ................................................................................................................................................................................................ 98 快速入门:LED 库使用方法 ................................................................................................................................................................ 98 LED 测试例程........................................................................................................................................................................................... 100 VCAN_KEY 模块 ............................................................................................................................................................................................. 102 快速入门:KEY 库使用方法 ............................................................................................................................................................. 102 KEY 测试例程........................................................................................................................................................................................... 106 UART 模块........................................................................................................................................................................................................ 111 山外论坛 http://vcan123.com 山外メ雲ジ ~7~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 快速入门:UART 库使用方法.......................................................................................................................................................... 111 UART 综合测试例程 ............................................................................................................................................................................. 118 I2C 模块 ............................................................................................................................................................................................................. 125 快速入门:I2C 通信库使用方法 ..................................................................................................................................................... 125 MMA7455 I2C 通信实验测试............................................................................................................................................................ 127 SPI 模块.............................................................................................................................................................................................................. 130 快速入门:SPI 通信库使用方法 ..................................................................................................................................................... 130 SPI 通信实验测试................................................................................................................................................................................... 133 VCAN_NRF24L01+ 模块 ............................................................................................................................................................................. 135 快速入门:NRF24L01+库使用方法 .............................................................................................................................................. 135 NRF24L01+无线通信实验测试 ........................................................................................................................................................ 140 K60_FTM/KL26_TPM 模块 ....................................................................................................................................................................... 146 快速入门:PWM 库使用方法........................................................................................................................................................... 146 FTM/TPM PWM 综合测试例程........................................................................................................................................................ 155 FTM/TPM 输入捕捉综合测试例程................................................................................................................................................. 159 FTM/TPM 脉冲计数综合测试例程................................................................................................................................................. 167 PIT 定时中断模块 ......................................................................................................................................................................................... 173 快速入门:PIT 定时中断库使用方法 ........................................................................................................................................... 173 PIT 测试例程 ............................................................................................................................................................................................ 177 LPTMR 低功耗定时器模块 ....................................................................................................................................................................... 183 快速入门:LPTMR 库使用方法....................................................................................................................................................... 183 LPTMR 测试例程.................................................................................................................................................................................... 188 ADC 模块........................................................................................................................................................................................................... 193 快速入门:ADC 库使用方法............................................................................................................................................................. 193 ADC 测试例程 .......................................................................................................................................................................................... 203 DAC 模块........................................................................................................................................................................................................... 205 快速入门:DAC 库使用方法............................................................................................................................................................. 207 DAC 综合测试例程 ................................................................................................................................................................................ 209 DMA 模块.......................................................................................................................................................................................................... 211 快速入门:DMA 传输端口数据....................................................................................................................................................... 211 DMA 测试例程 ......................................................................................................................................................................................... 216 K60_SDHC 模块(带文件系统 FatFs R0.09).................................................................................................................................. 220 FatFs 与 Petit FatFs 的介绍................................................................................................................................................................ 220 FatFs 文件系统的函数 API 接口 ..................................................................................................................................................... 221 FastFs 文件系统的磁盘 I/O 接口 ................................................................................................................................................... 222 文件函数代码的返回值列表.............................................................................................................................................................. 222 资料推荐 .......................................................................................................................................................................224 《轻松玩转 ARM Cortex-M4 微控制器-基于 Kinetis K60》 ..................................................................................................... 224 山外论坛 http://vcan123.com 山外メ雲ジ ~8~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 山外资料下载方法 ........................................................................................................................................................................................ 230 山外论坛 http://vcan123.com 山外メ雲ジ ~9~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 Kinetis 介绍 山外 飞思卡尔公司的 Kinetis 系列 ARM® Cortex™ MCU 由多款软硬件互相兼容的 ARM® Cortex™-M0+ 和 ARM® Cortex™-M4 MCU 产品构成,具有出色的低功耗表现、 内存扩展特性和功能集成。系列产品包括从入门级的 ARM® Cortex™-M0+ Kinetis L 系列到高性能、功能丰富的 ARM® Cortex™-M4 Kinetis K。 Kinetis 的 MCU 种类繁多,通过特定的命名规则来分区不同的型号芯片。 K 系列命名规则 本教程主要讲解 K60 和 KL26,K60 是 Cortex-M4 系列,KL26 是 Cortex-M0+系 列。根据第九、第十届的比赛规则,同一学校同一组别可同时使用 K60 和 KL26。 (比赛规则可能会有所变动,请及时关注组委会提供的最新规则) 山外论坛 http://vcan123.com 山外メ雲ジ ~ 10 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 山外提供的山外 K60/KL26 库,支持 K60 的 MK60DN512Z 和 MK60FX512,KL26 的 MKL26Z256VLL4 。 Cortex-M4 和 K60 是什么关系?为啥学 K60 单片机总是讲到 Cortex-M4 内核? (类似问题:Cortex-M0+和 KL26 是什么关系?为啥学 KL26 单片机总是讲到 Cortex-M0+内核?) 初次接触 ARM MCU 的读者可能搞不懂 Cortex-M4 内核和 K60 的关系,搞不懂 Cortex-M4 外设和 K60 外设的关系。 简单的说,K60 是一块 MCU 芯片,其内部还可以细分多个模块:Cortex-M4 内 核、GPIO、UART、I2C、SPI、ADC、DAC、DMA、PIT、FTM 等。除 Cortex-M4 内核 模块外,其他的 GPIO、UART 等模块就是 K60 的外设。 Cortex-M4 内核是 K60 芯片的核心,其内部还可以细分多个模块:Cortex-M4 CPU、DSP、NVIC、SysTick、SCB、MPU、FPU 等。除 Cortex-M4 CPU 和 DSP 外,其 他的 NVIC 等模块就是 Cortex-M4 外设。 ARM 公司是一家知识产权(IP)供应商,其本身是不制造芯片、不出售芯片,而 是通过转让设计方案,由合作伙伴生产出各具特色的芯片。飞思卡尔公司从 ARM 公 司获取到 ARM Cortex-M4 内核的授权,在 Cortex-M 内核的基础上增加外设而形成 Kinetis 系列单片机。这种模式也给用户带来巨大的好处,因为用户只掌握一种 ARM 内核结构及其开发手段,就能够使用多家公司相同 ARM 内核的芯片。 除了 ARM Cortex-M 内核架构外,其实还有很多的内核架构,例如 51、X86、MIPS、 RISC 等。学习过 51 单片机的读者也应该了解过不同公司的 51 单片机,他们都是采 用 51 内核,因此开发手段上基本相同。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 11 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K60 的调试/编程接口如下: 避免使用下载口,否则容易 出现下载不了的情况。 Kinetis 管脚 PTA0 PTA1 PTA2 PTA3 PTA4 JTAG TCK TDI TDO TMS — EzPort EZP_CLK EZP_DI EZP_DO — EZP_CS SWD SWD_CLK — SWD_DIO — — KL26 的调试/编程接口如下: Kinetis L 管脚 SWD PTA0 SWD_CLK PTA3 SWD_DIO KL26 仅支持 SWD 下载 山外论坛 http://vcan123.com 山外メ雲ジ ~ 12 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 Kinetis 的启动分析(初学者大概浏览一下即可) 初步入门:初始化函数启动执行顺序 上电后根据中断向量表来执行 复位函数 可以看到,在进入 main 函数前,单片机上电复位后,会开总中断,关闭看门狗, 初始化数据段,以及设置系统频率和初始化串口等各种操作。 注:common_startup 函数并没有复制 常量数据 .rodata 、代码.text 。如果是 RAM 启动,代码会直接编译进去 RAM,掉电就会丢失数据。如果是 ROM 启动,就会 复制中断向量表到 RAM,设置中断向量表地址为 RAM 的地址,以加快中断响应速度。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 13 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 逐步提高:ROM、RAM 启动工作原理、ICF 文件讲解 山外 对于初学者而言,对单片机的内存分配往往最让人头疼,很多人学了单片机几年 都不知道单片机内部的内存使用情况是如何分配的。要了解 ROM、RAM 启动,首先 需要对 链接器 Linker 如何分配内存有一定的了解。 通常,对于栈生长方向向下的单片机,其内存一般模型是: 最低内存地址 中断向量表 代码区 数据区 中断向量表段 .intvec 函数代码段 .text 未初始化变量段 .bss 常量段 .rodata 已初始化全局变量和静态变量段 .data 堆 栈 命令行参数 动态分配数据 程序运行中由程序员调用 malloc 等函数来申请。 局部变量 在函数内部定义的非静态的变量。 最高地址 1. int a = 0; //全局初始化区,.data 段 2. static int b=20; //全局初始化区,.data 段 3. char *p1; //全局未初始化区 .bss 段 4. const int A = 10; 5. volatile const int B = 10; //.rodata 段 //.data 段 注意区别!! 6. main() //代码区 .text 7. { 8. int b; //栈 9. char *p2; //栈 10. static int c = 0; //全局(静态)初始化区 .data 段 11. 12. char s[] = "123456"; char *p3 = "123456"; //栈 //123456\0 在常量区,p3 在栈上。 注意区别!! 13. p1 = (char*) malloc(10); //分配得来的 10 和 20 个字节的区域就在堆区 14. p2 = (char*) malloc(20); 15. strcpy(p1, "123456"); //123456\0 在常量区 16. //编译器可能会将它与 p3 所指向的"123456"优化成一个地方 17. } IAR 里可以设置 Linker 合并相同的段,即优化成一个地 方。看后面 IAR 设置工程选项中 Linker 的设置教程 注:此表格及代码内容参考网上资料 山外论坛 http://vcan123.com 山外メ雲ジ ~ 14 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 参考原文出处:http://blog.chinaunix.net/uid-15473693-id-388637.html 那编译到时候,编译器是如何为这些变量数据分配地址的呢? 其实,这就是链接器 Linker 在发挥它的作用,它会根据配置文件,来为这些变量 数据分配合适的地址,这样我们就可以不需要考虑这些内存分布就能写出可运行的代 码。 编译器对代码进行编译,一般分为四个步骤: 预编译 •替换和 展开宏 •删掉注 释 编译和 编译成汇 优化 编语言 汇编 汇编成机 器码 链接 生成可执 行文件 通常情况下,链接器 Linker 的配置文件都是由官方提供,一般情况下,我们不需 要更改这些。但出于学习的目的,我们非常有必要去研究一下这些配置文件(例如需 要用到 IAP 或 ISP 时就得需要自行修改)。 在 Prj\IAR\config files 文件夹下,你可以看到有很多的 linker 配置文件: 这里的文件是用来分配数据在内存中的位置,配置 ROM、RAM 启动,linker 根据 这些文件来为 Kinetics 分配 4G 的虚拟寻址空间地址。如果把代码部分编译进去 RAM, 那就是 RAM 启动;如果把代码数据编译进去 ROM,那就是 ROM 启动(flash 启动)。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 15 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 不同的型号,flash 内存大小不一样,所以配置 Linker 文件也会不一样,以 K60 为例: K60 的 4G 虚拟寻址空间就是按照内存空间的映射图来进行配置: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 16 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 如果要 RAM 启动,我们要把代码编译进 SRAM_L ,定义中断向量表的位置。 可以打开 vcan_RAM_K60N512.icf 这个文件(用记事本打开就行),这个配置文 件是参考 128KB_Ram.icf 来重新修改和添加了注释。 山外已经对配置文件进行修改,在里面添加了很多注释: /*###ICF### Section handled by ICF editor, don't touch! ****/ 中断向量表地址。Flash 和 SRAM 启 /*-Editor annotation file-*/ 动的中断向量表地址是不一样的 /* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */ /*-Specials-*/ define symbol __ICFEDIT_intvec_start__ = 0x1fff0000; //RAM 启动要设置为 RAM 起始地址,ROM 启动也要设为 ROM 的起始地址 /*-Memory Regions-*/ define symbol __ICFEDIT_region_ROM_start__ = 0x0; //ROM 地址 define symbol __ICFEDIT_region_ROM_end__ = 0x00040000; define symbol __ICFEDIT_region_RAM_start__ = 0x1fff0000; //RAM 地址 define symbol __ICFEDIT_region_RAM_end__ = 0x20000000; /*-Sizes-*/ define symbol __ICFEDIT_size_cstack__ = 0x1000; //堆大小 define symbol __ICFEDIT_size_heap__ = 0x800; //栈大小 /**** End of ICF editor section. ###ICF###*/ /**** 上边是由 ICF 编辑,下面是由我们手动配置 ****/ Kinetis 的 SRAM 是分成两块的。 这里是设置第二块的地址范围 define symbol __region_RAM2_start__ = 0x20000000;//SRAM 是分成两块的,RAM2 即 SRAM_U ,RAM 为 SRAM_L define symbol __region_RAM2_end__ = 0x20000000 + __ICFEDIT_region_RAM_end__ - __ICFEDIT_region_RAM_start__; 中断向量表地址 define exported symbol __VECTOR_TABLE = __ICFEDIT_intvec_start__; //代码编译进 ROM ,则 0x00000000 ;RAM,则 __ICFEDIT_region_RAM_start__ define exported symbol __VECTOR_RAM = __ICFEDIT_region_RAM_start__ ; //前面的 RAM 留给 RAM User Vector Table //如果是 ROM 启动,则 common_startup 函数会把 __VECTOR_TABLE 的数据复制到 __VECTOR_RAM 可以看出栈的生长方向为向下 define exported symbol __BOOT_STACK_ADDRESS = __region_RAM2_end__ - 8; //0x2000FFF8;启动栈地址,中断向量表的第一个元素就是指向这里 这里就是代码的编译地址。加上 /* 决定代码编译的地址 */ 0x410,是前面的留给中断向量表 define exported symbol __code_start__ = __ICFEDIT_intvec_start__ + 0x410; //代码编译进 ROM ,则 __ICFEDIT_region_ROM_start__ + 0x410 , //RAM,则 __ICFEDIT_region_RAM_start__ + 0x410 ,+0x410 ,是前面的留给 Vector Table define memory mem with size = 4G; //4G 的虚拟寻址空间 define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; 设置 ROM 和 RAM 的可用 内存地址范围 define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__] | mem:[from __region_RAM2_start__ to __region_RAM2_end__]; define block CSTACK define block HEAP with alignment = 8, size = __ICFEDIT_size_cstack__ { }; //堆,8 字节对齐 with alignment = 8, size = __ICFEDIT_size_heap__ { }; //栈,8 字节对齐 //手动初始化,在 common_startup 函数 里完成 设置堆栈的大小 山外论坛 http://vcan123.com 山外メ雲ジ ~ 17 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 initialize manually { readwrite }; initialize manually { section .data}; initialize manually { section .textrw }; // 未初始化数据 .bss // 已初始化数据 // __ramfunc 声明的子函数 do not initialize { section .noinit }; // 复位中断向量服务函数 山外 取消自动初始化,改为由 common_startup 函数来初始化 define block CodeRelocate { section .textrw_init }; define block CodeRelocateRam { section .textrw }; //CodeRelocateRam 把代码复制到 RAM 中(对 flash 操作的函数必须这样) 设置中断向量表位置放在 __ICFEDIT_intvec_start__ 地 址上 place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; //vectors.c 中设置 #pragma location = ".intvec" ,告诉编译器,这个是中断向量表,编译进去 .intvec place at address mem:__code_start__ { readonly section .noinit }; //在 crt0.s 中设置了复位中断函数为 SECTION .noinit : CODE 设置 .noinit 段放在代码开头, 即复位中断服务函数,把复位 函数编译在代码开头 ,即把代码编译进去 .noinit RAM 启动,会把只读数据、代码都编 译进去 RAM place in RAM_region place in RAM_region { readonly, block CodeRelocate }; //把代码编译进去 RAM (调试用,掉电丢失) ,非调试,则设为 ROM_region { readwrite, block CodeRelocateRam, block CSTACK, block HEAP }; Linker 根据 icf 配置文件来进行分配内存地址: 编译器根据 icf 文件来把中断向量表分 配到 Ram 区域,在线调试时由调试器 加载 SP 和 PC 的值。 0x0000-0000 0x0007-FFFF Flash 512KB …… 0x0000-0000 0x0000-0400 0x0000-0410 ROM Bootloader 中断向量表 Flash 配置寄存器 0x1FFF-0000 RAM Bootloader 中断向量表 RAM 配置寄存器 Code 中断向量表 代码区 数据区 堆 …… 0x1FFF-0000 SRAM 128k 堆 0x2001-0000 栈 0x2000-FFF8 配置 0x2001-0000 栈 命令行参数 如果是 ROM 启动(flash 启动): 中断向量表地址。Flash 和 SRAM 启 动的中断向量表地址是不一样的 1. /*###ICF### Section handled by ICF editor, don't touch! ****/ 2. /*-Editor annotation file-*/ 3. /* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */ 4. /*-Specials-*/ 5. define symbol __ICFEDIT_intvec_start__ = 0x00000000; 山外论坛 http://vcan123.com 山外メ雲ジ ~ 18 ~ 6. /*-Memory Regions-*/ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 设置 P-flash 和 D-flash 的 分区大小 7. define symbol __ICFEDIT_region_ROM_start__ = 0x0; 8. define symbol __ICFEDIT_region_ROM_end__ = 0x00040000; 9. //0x00040000:P-flashk 256k D-flash 256k 0x00080000:P-flashk 512k 10. define symbol __ICFEDIT_region_RAM_start__ = 0x1fff0000; 11. //前面的 0x410 RAM 留给 RAM User Vector Table 。 12. define symbol __ICFEDIT_region_RAM_end__ = 0x20000000; 13. /*-Sizes-*/ 14. define symbol __ICFEDIT_size_cstack__ = 0x2000; 15. define symbol __ICFEDIT_size_heap__ = 0x2000; 设置堆栈大小 16. /**** End of ICF editor section. ###ICF###*/ 17. 18. 19. /**** 上边是由 ICF 编辑,下面是由我们手动配置 ****/ 20. 21. define symbol __region_RAM2_start__ = 0x20000000; 22. //SRAM 是分成两块的,RAM2 即 SRAM_U,RAM 为 SRAM_L 23. define symbol __region_RAM2_end__ = 0x20000000 + __ICFEDIT_region_RAM_end__ 24. - __ICFEDIT_region_RAM_start__; 25. 26. 27. define exported symbol __VECTOR_TABLE = __ICFEDIT_intvec_start__; 28. //代码编译进 ROM ,则 0x00000000 ;RAM,则 __ICFEDIT_region_RAM_start__ 29. define exported symbol __VECTOR_RAM = __ICFEDIT_region_RAM_start__; 30. //前面的 RAM 留给 RAM User Vector Table,即这里的设置。所以减 0x410 31. //common_startup 函数就是把 __VECTOR_TABLE 的数据复制到 __VECTOR_RAM 32. 33. define exported symbol __BOOT_STACK_ADDRESS = __region_RAM2_end__ - 8; 34. //0x2000FFF8; 启动栈地址 35. 36. /* 决定代码编译的地址 */ 37. define exported symbol __code_start__ = __ICFEDIT_intvec_start__ + 0x410; 38. //+0x410 ,是前面的留给 Vector Table 39. 40. define memory mem with size = 4G; //4G 的虚拟寻址空间 41. 42. define region ROM_region = 43. mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; 44. 45. define region RAM_region = 46. mem:[from __ICFEDIT_region_RAM_start__ + 0x410 to __ICFEDIT_region_RAM_end__] 47. | mem:[from __region_RAM2_start__ to __region_RAM2_end__]; 48. 49. 50. define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { }; //堆 51. define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { }; //栈 52. 53. //手动初始化,在 common_startup 函数 里完成 54. initialize manually { readwrite }; // 未初始化数据 .bss 堆栈大小 55. initialize manually { section .data}; // 已初始化数据 56. initialize manually { section .textrw }; // __ramfunc 声明的子函数 57. 58. do not initialize { section .noinit }; // 复位中断向量服务函数 59. 60. define block CodeRelocate { section .textrw_init }; 61. define block CodeRelocateRam { section .textrw }; 62. // 把 CodeRelocate 代码复制到 RAM 中的 CodeRelocateRam (对 flash 操作的函数必须这样) 63. 64. place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; 65. //vectors.c 中设置#pragma location = ".intvec",告诉编译器,这个是中断向量表,编译进去 .intvec 66. 67. place at address mem:__code_start__ { readonly section .noinit }; 68. //在 crt0.s 中设置了 Reset_Handler 为 SECTION .noinit : CODE 69. //即把 Reset_Handler 编译进__code_start__ 70. 71. place in ROM_region { readonly, block CodeRelocate }; 72. //把代码编译进去 ROM (调试用) ,非调试,则设为 ROM_region 73. 74. place in RAM_region { readwrite, block CodeRelocateRam, ROM 启动,就要把只读数据和 代码编译进 ROM 山外论坛 http://vcan123.com 山外メ雲ジ ~ 19 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 75. block CSTACK, block HEAP }; Linker 根据 icf 配置文件来进行分配内存地址: common_startup 函数 把中断向量表、已 初始化数据 复制到 RAM,以__ramfunc 声 明的子函数,以加快速度。以后发生中断 就会用 RAM 的中断向量表 0x0000-0000 0x0007-FFFF Flash 512KB …… 0x1FFF-0000 0x2001-0000 SRAM 128k 0x0000-0000 0x0000-0400 0x0000-0410 ROM Bootloader 中断向量表 Flash 配置寄存器 Code 0x1FFF-0000 数据区 .rodata RAM Bootloader 中断向量表 0x1FFF -0410 RAM 配置寄存器 Code RAM 数据区 .bss 和 .data 堆 0x2000-FFF8 0x2001-0000 栈 配置 中断向量表 代码区 数据区 堆 栈 命令行参数 前面的内容,不知道大家是否都看懂呢?熟悉单片机的内存分配,是每一个电子 软件工程师所必备的。如果对单片机的内存分配都不了解,那就算会写单片机的驱动 程序,也难以保证程序的质量。 那在 IAR 里,怎么知道编译生成的文件的内存图? 在工程选项里,设置生成 map file 就行: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 20 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 设置好,编译后,在 Prj\IAR\xxx 模式\List 文件夹下就会有 .map 文件。 这里的内容,就是 ICF 配置文 件底部的宏替换内容 中断向量表 复位中断服务函数 程序代码 山外论坛 http://vcan123.com 山外メ雲ジ ~ 21 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 那什么是 ICF 编辑器呢? 在工程名里右键——Options——Linker —— Config —— Edit 山外 中断向量表地址 这里就是设置 Linker 配置文件 的 ICF 编辑器,可以在里面设置 相应的数据 山外论坛 http://vcan123.com 山外メ雲ジ ~ 22 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 ROM、RAN 地址 堆栈大小 山外论坛 http://vcan123.com 山外メ雲ジ ~ 23 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 IAR 的使用 山外 相对于 Keil For ARM、CodeWarrior 而言,IAR for ARM 的编程界面是最简单的, 编译效率高,,调试功能也最为强大,因此山外强烈推荐大家用的是 IAR。 山外推荐的是直接在山外提供的 K60 库模版上添加你们自己的代码,而不是自己 重建工程,但学会自己建立工程是必须的,在新建工程的过程中可以学习到 IAR 工程 的设置,不然后面出了问题,就没法自行解决了。 这里,山外给大家详解建立 IAR 工程的步骤。 注意一点,编译器总是频繁更新版本,通常都是新版本编译器兼容旧版本工程, 而旧版本编译器没法打开新版本工程,或者打开出错。 如果打开工程出错,要不从小到山外论坛下载原始版本工程,要不把 IAR 升级到 最高版本。 安装 IAR IAR 安装包,读者可自行网络下载。 K60 可用 IAR6.3 或以上的版本,KL26 可用 IAR6.7 或以上版本。部分电脑在安装 IAR 后,会出现 IAR 卡死问题,即使用管理员 身份运行 IAR 也没法解决,那么可通过 使用更高版本 IAR 来解决。 这里是山外的 IAR 常见资料下载和使用方法汇总专辑: http://www.vcan123.com/forum.php?mod=collection&action=view&ctid=34&fr omop=all K60 的用户,在安装 IAR 后,需要按照这里的方法一说明操作一下: IAR 6.5 以上版本 打开 山外工程失败的解决方法 - 智能车资料区 - 山外论坛 山外论坛 http://vcan123.com 山外メ雲ジ ~ 24 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 http://vcan123.com/forum.php?mod=viewthread&tid=1237&ctid=1 山外 附带其他有用帖子: Jlink 驱动下载地址: 【山外资料】jlink 驱动 - 智能车资料区 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=6233&ctid=26 Jlink 驱动里面:flash ARM 用于下载程序,或者擦除芯片。jlink commander 是常 用于解锁。其他的功能,我们一般用不到。 Jlink 解锁步骤,请看此帖子第 4 步: 【山外 K60 KL26】jlink 下载失败的最详细解决办法 - 智能车资料区 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=84&ctid=1 山外论坛 http://vcan123.com 山外メ雲ジ ~ 25 ~ 建立 IAR 工程 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 创建工程文件 一、 准备材料 工程,本身需要有相关的启动代码等等。为了简化建工程的步骤,山外直接从山 外建好的工程里提取相应的代码,在这基础上建立工程。 本节,我们通过建立 IAR 工程,编程实现对 GPIO 的操作,从而实现点亮 LED。 本节内容是不讲解代码的,需要用到的代码都直接从现有的 “第二章\GPIO_LED”工 程里提取出来,后续的学习中自然熟悉这些代码的实现原理。 在建立 IAR 工程前,需要下载山外 K60 例程,提取里面的配置文件和写好的库。 相关资料可在《山外资料下载》:(搜索关键字“代码”) http://vcan123.com/forum.php?mod=collection&action=view&ctid=26 K60 例程包里,书后例程都带完整的库代码,而第 x 章则为逐步添加代码,非完 整库代码。 KL26 每个例程都带完整库代码。 这里以 K60 为例,其实 KL26 也是同一个方法。 解压后,打开目录,会看到两个文件夹 二、 建立 IAR 工程过程 山外论坛 http://vcan123.com 山外メ雲ジ ~ 26 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 在这里,我们以 GPIO 为例子,点亮 LED,一步步操作,讲解建立工程的详细过 程。 1. 建立工作空间 在工具栏里选择 File——New——Workspace 2. 建立工程 在工具栏里选择 Project——Create New Project 山外论坛 http://vcan123.com 山外メ雲ジ ~ 27 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 接着在弹出来的对话框里选择空的工程,点击确定 接着弹出选择保存工程的对话框。由于我们还没建保存工程的文件夹,我们就直 接在对话框里新建:右键——新建——文件夹——重命名为:fire_Kinetis(这个自行定 义,例如可以改成 vcan_k60 ) 山外论坛 http://vcan123.com 山外メ雲ジ ~ 28 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 为了使得工程架构更加清晰,因此我们需要对工程文件夹进行如下规定: ├─App │ └─Inc ├─Board │ ├─inc │ └─src ├─Chip │ ├─inc │ │ ├─IAR │ │ └─kinetis │ └─src │ └─IAR ├─Lib └─Prj └─IAR └─config files 用户程序 用户程序头文件 开发板驱动程序 开发板驱动程序头文件 window 系统是不区 分文件大小写 开发板驱动程序 K60 芯片驱动程序 K60 芯片驱动程序头文件 K60 芯片驱动程序与 IAR 编译器相关头文件 K60 芯片驱动程序与 Kinetis MCU 相关头文件 K60 芯片驱动程序 K60 芯片驱动程序与 IAR 编译器相关程序 现成库代码 工程文件 IAR 工程文件 IAR 编译器相关的配置文件 山外论坛 http://vcan123.com 山外メ雲ジ ~ 29 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 打开新建的 fire_Kinetis 文件夹,根据上述的工程文件架构来创建文件夹。把工程 文件保存在 Prj\IAR 文件夹里面,工程文件名为 :fire_demo。 保存 IAR 工程后,进入 IAR 界面,还需要在菜单栏里找到图标 ,保存工作区 文件,命名为 fire_demo。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 30 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 为了方便在工程根目录打开 IAR 工程,我们可以把工作区文件 fire_demo.eww 复 制到工程根目录,用记事本打开根目录下的 fire_demo.eww 文件,修改里面的工程路 径。$WS_DIR$为工作区所在的文件夹。 $WS_DIR$\fire_demo.ewp 修改为: $WS_DIR$\Prj\IAR\fire_demo.ewp 修改相对路径 至此,一个空的 IAR 工程骨架就建立完毕。我们开始往里面添加自己的模块。 3. 往工程添加山外写好的函数库 复制第二章\GPIO_LED 例程下的 App、Board、Chip、Lib 文件夹到新建的 IAR 工 程里。 复制第二章\GPIO_LED 例程下的 Prj\IAR\config files 里全部文件到新建的 IAR 工程相应文件夹下。 KL26 可选用其他任何一个工程。 现成的代码是如何实现的,这个留到后续的学习中讲解,本节内容不讲解代码。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 31 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 细心的读者可能发现工程下会多了个 settings 文件夹,这个是保存工作区后 IAR 自动创建的设置文件夹,可删掉,删掉后会自动生成,我们不需要管它。 4. 添加分组,方便管理代码 返回 IAR 界面,在工程里创建分组 在弹出来的对话框里填入 “app” ,这样就添加了一个 app 分组: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 32 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 如此继续添加其他分组:Board、Chip、Lib 山外 这些分组有什么用呢?其实分组就好比于文件夹,由于我们的代码比较多,利用 分组来区分不同模块的代码,从而方便我们查找代码。 现在,我们就往里面添加源代码文件,添加后,我们就明白分组的用途。 5. 把代码文件放进分组 把 fire_kinetis\App 文件夹 下的 *.c 文件全部添加到 App 分组里: 选中所有的 *.c 文件。如果你想把头文件也放在工程里,也可以把 *.h 文件选中。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 33 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 由于上图的 3 个头文件比较常用和重要,因此也把下列 3 个头文件放入工程 App 分组里。 添加后: 再把 Borad 和 Chip 文件夹 下的 *.c 都添加到相应分组里,Chip\Src\IAR 下有个 *.s 文件,也需要添加到工程分组里: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 34 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 startup_MK60DZ10.s 是启动文件,单片机上电复位后,就会执行里面的汇编代 码。 Lib 里是存放现有的代码库,目前仅有 CMSIS 库,而 CMSIS 库是已经编译好的库 文件,可以选择把库文件放入分组里,也可以在工程选项里添加,两种方法都可行。 我们选择在工程选项里加入,因此此处不需要添加文件。 至此,IAR 工程里添加文件到此完成。后续我们还需要修改工程选项配置。 IAR 工程选项配置 编译模式 IAR 的工程选项默认分成两个:Debug 模式(调试模式) 和 Release 模式(发布 模式): 山外论坛 http://vcan123.com 山外メ雲ジ ~ 35 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 在调试的时候,我们可以选择 Debug;发布产品或者比赛的时候,我们可以选择 Release ,这样切换起来非常方便。 网上共享的一些 IAR 工程,有时读者会发现他们并没有 Debug 模式 和 Release 模式,取而代之的是 ROM 模式、RAM 模式、flash 模式等等,这些模式名字都是可以 自行修改或增减:Project —— Edit Configurations: 点击后,会弹出如下界面: 在弹出来的界面里选择:new —— 然后命名和设置成自己所需要的: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 36 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 注意,从 Debug 模式切换到 Release 模式,会经常出现各种异常的编译错误或者 运行异常,这是因为 Release 有更严格的检查,详细情况可看这篇教程:光盘资料\资 料手册\在 IAR 的 Workspace 窗口顶部的下拉菜单中有两个选项.pdf。 网上共享的工程模版,改名字后的模式一般都是基于 Debug 模式,主要就是担心 用户编译容易出错(Release 模式比 Debug 模式更加严格的语法检查和优化,因而容 易出问题)。Debug 模式的优化效果不如 Release 模式,但 Debug 模式调试方便,因 此两个模式各有优点。 在建工程的时候,我们需要根据两种不同的模式进行不同的配置。在这里,我们 就用 Debug 模式来讲解。 在工作区的工程名上:右键——Options 随后就会弹出选项框: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 37 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 注意,是在工程名里右键,不要在分组名里右键哦,不然弹出的对话框不是设置 工程的,而是设置分组的。 好了,现在开始配置工程选项了。 General Options ——Target 设置芯片型号的步骤 设置芯片型号 山外 K60 库,目前支持 MK60DN512ZVLQ10 和 MK60FX512VLQ15。注意芯片型 号带 Z 和不带 Z 是不相同的。 山外 KL26 库,目前支持 MKL26Z256VLL4。 我们需要根据根据自己使用的芯片型号来选择,例如 MK60DN512ZVLQ10 的选择: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 38 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 MKL26Z256VLL4 选择: 山外 General Options ——output IAR 可选是生成库文件或者可执行文件 设置输出信息 我们生成可执行文件,下载到单片机上执行,因此选择 Executable。 General Options ——Library Configuration 库配置 配置 IAR 自带的库的支持类别,如果选择 Full 则可支持更多的功能,例如支持 printf 函数。本身配套的工程使用的 printf 函数是 IAR 库自带代码,因此此处需要配 置为 Full。 由于 CMSIS 库默认屏蔽了 NVIC 等模块的函数功能,需要修改源代码才可支持这 些功能,而 IAR 自带的库是放在 IAR 安装目录,不利于拷贝代码到其他电脑,因此不 使用 IAR 自带的 CMSIS 库。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 39 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 C/C++ Compiler ——Language 2 设置 char 类型 IAR 的 char 类型默认配置为 unsigned 型,而我们习惯上认为是 Signed 型。因需 要配置 char 的数据类型: C/C++ Compiler —— Optimizations 优化等级 在 Debug 模式里,这里设为不优化,便于调试方便。事实上,在调试的时候,如 果编译器进行了优化,有些变量就被优化了而不能显示出来,就不便于调试了。 在 Release 模式里,可以选择最大优化,但在发布前,需要对优化后的效果进行 验证。因为优化后可能会出现各种异常的错误。 用户可以根据自己的需要配选择不同的优化等级: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 40 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 C/C++ Compiler —— Preprocessor 预处理器 在 Additional include directories: (one per line) 文本编辑框里填入: $PROJ_DIR$\..\..\App $PROJ_DIR$\..\..\App\Inc $PROJ_DIR$\..\..\Board\Inc $PROJ_DIR$\..\..\Chip\inc $PROJ_DIR$\..\..\Chip\inc\IAR $PROJ_DIR$\..\..\Chip\inc\kinetis $PROJ_DIR$\..\..\Lib\CMSIS\Inc 在这里填入的是头文件所在的文件夹,预处理器编译的时候,就根据这些路径来 搜索头文件。这里需要填入相对地址,如果填入绝对地址,那么修改工程路径后就会 编译报错。 $PROJ_DIR$ 表示 IAR 工程所在的目录 (Prj\IAR) ..\ 表示上一层目录 在 Defined symbols: (one per line) 文本编辑框里填入: DEBUG ARM_MATH_CM4 MK60DZ10 这个是用于定义宏,对整个工程有效,相当于在代码里: #define DEBUG 山外论坛 http://vcan123.com 山外メ雲ジ ~ 41 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 #define ARM_MATH_CM4 山外 #define MK60DZ10 上述填入的 DEBUG 是根据工程模式来选择,如果是在 Debug 模式就选择 DEBUG, 如果是 Release 模式就选择 NDEBUG。 上述填入的 ARM_MATH_CM4 是 CMSIS 库里需要用到,用于声明使用的是 Cortex-M4 内核。 KL26 是 Cortex-M0+内核,则改为:ARM_MATH_CM0PLUS 上述填入的 MK60DZ10 是根据芯片类型来选择,如果是 MK60DN512ZVLQ10 芯 片就选择 MK60DZ10,如果是 MK60FX512VLQ15 芯片就选择 MK60F15。如果是 MKL26Z256VLL4 就选择 MKL26Z4。 如果勾上 Preprocessor output to 前面的勾后,那么编译后就会在 List 文件夹里 生成*.i 文件,这个是宏定义展开后的文件,有利于我们分析宏定义代码,例如 MK60_DWT.c 文件编译后生成的 MK60_DWT.i 文件内容如: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 42 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 不过由于编译后生成的文件较大,一般为了加快编译而不选择此选项。 Output Converter —— Output 输出格式转换 这里可以设置编译代码后,把代码转化成其他格式,由于 hex 格式更为通用(不 需要像 BIN 文件那样需要配置起始地址),因此我们需要设置转换为 hex 格式: Linker —— Config 链接器配置 Linker 的配置文件通过如下方式来指定。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 43 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 在 Linker configuration file 里勾中 Override default ,填入 icf 文件的相对路径。 icf 文件是 Linker 的配置文件,可以设置 Kinetis 单片机的代码分布,实现 Kinetis 单片 机的 RAM 启动和 ROM 启动等功能。 Linker 配置文件都是放在 Prj\IAR\config files 文件夹里,如果是 ROM 启动,则根 据芯片的 flash 容量来选择 Pflash;如果是 RAM 启动则根据芯片的 ram 容量来选择 RAM。vcan_RAM_K60N512.icf 和 vcan_ROM_K60N512.icf 则是由山外根据飞思卡尔 公司的例程修改而来,添加了注释,方便读者理解。(其他 K60FX 和 KL26 也是一样 的方法) 直接通过 IAR 的浏览框来选择的时候,编译器会改成绝对地址的,但为了工程能 拿到其他电脑上就能直接运行,建议手动修改成相对地址。相对地址的用法如下: $PROJ_DIR$ 表示 IAR 工程所在的目录 ..\ 表示上一层目录 Linker —— Automatic runtime library s 自动运行 库 如下图所示,这里设置库文件的路径。需要调用已经编译好的库的函数时,可在 此处填入库文件路径。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 44 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 Linker —— Optimizations 优化 如下图所示,这里可以进行一些优化处理。内联短小的函数则会提高运行效率而 增大占用空间,合并重复的段则减少内存空间而可能降低运行效率,需要根据自己需 求来取舍。 Linker —— List 列表 如下图所示,这里可以生成关于内存分布、编译后生成文件大小等各种信息的文 件。 以 Debug 模式为例,勾上 Generate linker map file ,编译后,在工程的文件夹 下生成:fire_kinetis\Prj\IAR\Debug\List\ fire_demo.map 文件。从 map 文件里可以 获取函数的访问地址,全局变量的存储位置、内存的分布情况等信息: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 45 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 Debugger —— Setup 仿真器设置 在 Driver 里有多种可供选择的调试方式。本书配套的山外 K60/KL26 开发板使用 jlinkV8 仿真器,因此可以在这里选择 J-Link/J-Trace 。 Simulator 是软件仿真,不过软件仿真存在较多的问题,建议直接硬件在线调试。 Debugger —— Download 此处用于配置调试器下载的相关内容。 下载设置 Verify download 这个选项用来验证下载的代码映像,可以正确的从 memory 空 间中读出。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 46 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 Suppress download 这个选项用来调试已经驻于 memory 空间中的应用程序。当 选择这个选项时,代码 将不会被下载,而会保留 flash 中的当前内容。 Use flash loader 这个选项用来指定下载代码到 flash 中所用的一个或多个 flash loader。 我们勾上 Verify download 和 Use flash loader 。 使用 MK60DN512ZVLQ10 芯片则需要填入 $TOOLKIT_DIR$\config\flashloader\Freescale\FlashK60Xxxx.board ,这个是 IAR 6.3 自带的 K60 Flash loader。 由于 IAR 6.3 版本没有 MK60FX512VLQ15 芯片的 Flash loader ,因此需要按如下 帖子方法一操作: IAR 6.5 以上版本 打开 山外工程失败的解决方法 - 智能车资料区 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=1237&ctid=1 复制后,填入 $TOOLKIT_DIR$\config\flashloader\Freescale\FlashK60Fxxx128K.board。 注意一点,如果读者想进行 RAM 启动,那么就不能勾上使用 flash loader,因为 flash loader 仅使用于 flash。 J-Link/J-Trace —— Setup 这里配置 jlink 的下载速度等相关内容。 jlink 下载设置 山外论坛 http://vcan123.com 山外メ雲ジ ~ 47 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 J-Link/J-Trace —— 这里配置 jlink 的下载方式。 Connection Jlink 连接设置 这里可以选择 Jlink 的下载方式:JTAG 和 SWD。山外在实际测试中发现,部分 jlink V8 版本使用 SWD 方式下载会容易出现 K60 芯片锁住的情况,因此建议使用 JTAG 方式来进行下载。 KL26 仅支持 SWD 下载,所以只可选 SWD。 到目前为止,IAR 重要的设置就介绍得差不多了,大家可根据上述的设置方式来 配置 Debug 模式(调试模式) 和 Release 模式(发布模式)。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 48 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 IAR 使用教程 其实在打开 IAR 软件的时候,已经弹出了 IAR 使用教程界面给你: 山外 IAR 入门使 用教程 用户指南 工程例程,有飞 思卡尔 Kinetis 系 列的例程哦! 嵌入式系统插 件 飞思卡尔 Kinetis 系列的例程: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 49 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 提供的都是英文版的,提高你们英语水平的时候有到了…… 山外就来个中文版的简单介绍吧,当然远不然官方的介绍那么详细。 工具栏功能介绍 IAR 的工具栏包含: 新建保存复制功能、查找替换功能、跳转功能、编译下载功能。 与其他编译器项目,IAR 的工具栏功能简单,容易上手  新建保存复制功能为: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 50 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 :新建空白文件 :复制 :打开文件 :粘贴 :保存文件 :打印 :保存所有文件 :撤销 :剪切 :恢复  查找替换功能为: :查找内容,这里为查找 fire 文字 :查找上一个 :查找下一个 :查找 :替换  跳转功能为: :光标跳到指定行列 :设置书签 :调到下个书签 :跳回上个页面 :跳到下个页面  编译下载功能为: :编译当前文件 :编译整个工程 :取消编译 :设置断点 :下载并调试 :不下载,直接调试 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 51 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 通过 jlink 下载并调试 设置工程选项下载方式为 :J-Link/J-Trace 山外 Jlink 有两种下载方式:JTAG 和 SWD 山外论坛 http://vcan123.com 山外メ雲ジ ~ 52 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 然后点击工具栏的 按钮,下载并调试 或者 按钮,直接调试,不下载: 闪烁一下下载窗口: 然后就进入调试界面: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 53 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 调试的工具栏: :复位 :暂停 :步过,执行函数,不进入函数内 :步进,跳进函数内 :步出,跳出函数 :下一条语句 :跳到光标所指向的语句 :全速运行 :退出调试 在菜单栏里选择 view ,就可以看到很多的调试工具。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 54 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 如果想看变量的值,可以直接光标选中该变量,然后右键——add to watch 调试功能跟其他编译器类似。 用 Jlink 解锁 Kinetis 当芯片进入安全保护状态,就需要擦除芯片就才能继续烧写程序,烧写时会提示: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 55 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 直接点击是就会自动帮你擦除芯片,然后就能下载程序。 如果上面的解锁没法解决,可看这帖子: 【山外 K60 KL26】jlink 下载失败的最详细解决办法 - 智能车资料区 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=84&ctid=1 另外,也不要用带 USB3.0 的 USB 接口来接 Jlink、BDM 等下载器,容易出错, 甚至下载不了。 IAR 界面风格设计 在菜单栏——Tools——Options 山外论坛 http://vcan123.com 山外メ雲ジ ~ 56 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 这里可以配置自动 对齐的间距 设置 Tab 键是插入 Tab 还是插入空格 设置各种字体风格 山外论坛 http://vcan123.com 山外メ雲ジ ~ 57 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K60/KL26 库的使用 山外 快速开发指南 BUG 更新汇总 代码总是存在 bug,后续的 bug 更新会在如下地址进行更新: 【一天攻破 K60】 BUG 汇总 - 一天攻破 K60 - 山外-vcan123 论坛 http://vcan123.com/forum.php?mod=viewthread&tid=1294&ctid=1 【一天攻破 KL26】BUG 汇总 - 智能车资料区 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=1468&ctid=9 快速入门:了解山外 K60/KL26 工程 山外 K60/KL26 工程文件讲解 例程架构:. │ ├─App │ └─Inc ├─Board │ ├─inc │ └─src ├─Chip │ ├─inc │ │ ├─IAR │ │ └─kinetis │ └─src │ └─IAR ├─Lib │ └─CMSIS │ └─Inc └─Prj └─IAR └─config files App 文件夹存放用户代码 App/inc 文件夹存放用户代码的头文件 Board 文件夹用于存放山外 K60/KL26 开发板配套的 外设驱动。 Chip 文件夹存放飞思卡尔 K60/KL26 芯片的芯片驱 动。 Lib 文件夹存放第三方库。 Prj 文件存放项目工程的配置文件。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 58 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 山外提供的 K60 例程同时支持 K60DZ10 和 K60FX15,可以在工程选项里选择对 应的工程。两款 K60 的代码是不能混用,否则会锁住芯片的,因此需要选择正确的芯 片。 KL26 工程目前仅支持 KL26,所以不需要上诉的选择。 文件名 常用的文件说明 作用 App\Inc\MK60_conf.h 山外 K60/KL26 库的配置文件,包含常见的 print 函数波 特率配置、频率设置等。 App\MK60_conf.c 断言函数、printf 底层接口、默认中断等常见函数。 App\MK60_it.c 从原先旧版本代码 isr.c 文件改名而来,可以按照旧版本 代码来编写中断函数。 App\Inc\ MK60_it.h 同上 App\Inc\PORT_cfg.h 管脚复用配置文件,用于配置 K60/KL26 芯片模块的默认 复用管脚。 注:上述的文件名仅列出 K60 的文件名,KL26 需要把 MK60_前缀改为 MKL_ 山外论坛 http://vcan123.com 山外メ雲ジ ~ 59 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 为了快速打开这几个文件,5.1 以上版本的工程都已经把这几个文件添加到 IAR 工作区的 APP 分组里: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 60 ~ 快速复用管脚 攻城略地之一天攻破 K60/KL26 —— 松飞科技 K60 管脚复用问题 配置 - 智能车资料区 - 山外论坛 http://vcan123.com/forum.php?mod=viewthread&tid=18&ctid=1 山外 山外 K60/KL26 库,本身已经设置了默认的各个模块通道所对应的管脚。在 App\Inc\PORT_cfg.h 文件里进行设置: 只需按照注释里写着的可选范围进行修改这里的宏定义,就能更改默认的复用管 脚,不需要修改函数。 对于 ADC 模块,由于每个通道都固定一个端口,不可配置 复用,因此直接在 Chip\Inc\MK60_ADC.h 或 Chip\Inc\MKL_ADC.h 文件里查询每个通道所对应的端口: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 61 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 62 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 中断函数的编写方法 山外 Cortex-M 系列的单片机都是通过 NVIC 模块来控制中断配置(设定中断函数的优 先级、使能中断响应等),通过 SCB 模块来设定中断向量表的地址。 Kinetis 单片机上电时默认中断向量表的起始地址为 0 地址,可在运行时通过寄存 器配置来设定为其他内存地址。山外 K60/KL26 的工程代码里会在系统初始化时复制 中断向量表的内容到 RAM 起始位置,然后设置寄存器来指定中断向量表的地址就是 RAM 起始地址。把中断向量表移到 RAM 的好处是加快中断响应,因为 RAM 的读写速 度要快于 Flash 的读写速度。 中断函数的编写实现方法有两个,一个是直接写入到 0 地址的中断向量表,一个 是运行时调用函数来写入到相应的 RAM 地址。 推荐用方法二!!! 方法一:直接把函数入口地址写入到 0 地址的中断向量表 5.0 之前的版本都是用此方法。 中断向量表__vector_table 在工程根目录\Chip\src\vectors.c 里定义: 中断向量表里面的元素都是通过宏定义来映射的 ,VECTOR_xxx 在工程根目录 \Chip\Inc\vectors.h 里定义,默认都是定义为 default_isr 函数: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 63 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 default_isr 函数在工程根目录\App\MK60_conf.c 里定义,只有在 DEBUG 模式下 才会执行函数代码,否则直接退出。它主要的功能是实现 LED1 闪烁,串口打印中断 信息,从而让开发者及时地发现问题,并解决问题。 /*! * @brief 默认中断服务函数 * @since * @note v5.0 此函数写入中断向量表里,不需要用户执行 */ void default_isr(void) { #ifdef DEBUG #define VECTORNUM \ 中断号可在 Cortex-M 内核的 SCB 模块 ICSR 寄存器 VECTACTIVE ((SCB_ICSR & SCB_ICSR_VECTACTIVE_MASK)>>SCB_ICSR_VECTACTIVE_SHIFT) //等效于 (*(volatile uint8_t*)(0xE000ED04)) uint8 vtr = VECTORNUM; 山外论坛 http://vcan123.com 山外メ雲ジ ~ 64 ~ led_init(LED1); 攻城略地之一天攻破 K60/KL26 —— 松飞科技 while(1) LED1 闪烁表示 { led_turn(LED1); DEBUG_PRINTF( "\n****default_isr entered on vector %d*****\n\n%s Interrupt", vtr, vector_str[vtr]); 山外 DELAY_MS(1000); } 打印中断信息和中断 号,以便及时查找问 #else return; #endif } 如果没定义 DEBUG,则直接 编写中断函数,我们可以在在工程根目录\App\MK60_it.c 文件里像编写普通函数 那样来编写。中断函数的参数类型必须是为 void,返回值类型必须是为 void,格式如 下: void func(void); 编写好相应的中断函数后,需要通过宏定义来重映射中断向量函数。中断向量表 函数的重映射在工程根目录\App\inc\MK60_it.h 文件里完成。假定我们需要重定向 VECTOR_003 硬件上访这个中断,那么我们可以通过宏取消定义来取消原先的定义, 然后重新定义新的函数: #undef VECTOR_003 //先取消映射到中断向量表里的中 断函数地址宏定义 #define VECTOR_003 HardFault_Handler //重新定义硬件上访 山外论坛 http://vcan123.com 山外メ雲ジ ~ 65 ~ 中断服务函数 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 extern void HardFault_Handler(void); 部定义 //声明函数已经在外 为什么我们在 MK60_it.h 文件里就能够取消 vectors.h 文件里的宏定义呢?这是 因为中断向量表是在 vectors.c 文件里定义的,而 vectors.c 文件开头的头文件包含顺 序是先包含 vectors.h,然后再包含 MK60_it.h 来取消前面的宏定义,从而实现了中断 向量表的重映射。 #include "vectors.h" #include "MK60_it.h" 方法二:运行中调用函数来修改中断向量表 山外的工程里已经提供了 set_vector_handler 函数来在运行中修改中断向量表里 的中断函数入口。 /*! * @brief 设置中断向量表里的中断复位函数 * @since v5.0 * @warning 只有中断向量表位于 icf 指定的 RAM 区域时,此函 数才有效 * Sample usage: set_vector_handler(UART3_RX_TX_VECTORn , uart3_handler); //把 uart3_handler 函数添加到中断向量表 */ void set_vector_handler(VECTORn_t vector , void pfunc_handler(void)) { extern uint32 __VECTOR_RAM[]; 山外论坛 http://vcan123.com 山外メ雲ジ ~ 66 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 ASSERT(SCB->VTOR == (uint32)__VECTOR_RAM); //断言,检 测中断向量表是否在 RAM 里 直接传递函数指 __VECTOR_RAM[vector] = (uint32)pfunc_handler; 针,然后修改中断 } 山外 VECTORn_t 是中断号的枚举类型,在工程根目录\Chip\inc\ common.h 文件里定 义。 /* * 中断向量表编号声明 前面的为 Cortex-M4 */ typedef enum 内核规定的,每个 { /****** Cortex-M4 Processor Exceptions Numbers *****************************/ NonMaskableInt_VECTORn = 2 , /*!< 2 Non Maskable Interrupt */ HardFault_VECTORn = 3 , /*!< 3 Hard Fault */ MemoryManagement_VECTORn= 4 , /*!< 4 Cortex-M4 Memory Management Interrupt */ BusFault_VECTORn = 5 , /*!< 5 Cortex-M4 Bus Fault Interrupt */ UsageFault_VECTORn = 6 , /*!< 6 Cortex-M4 Usage Fault Interrupt */ SVCall_VECTORn = 11, /*!< 11 Cortex-M4 SV Call Interrupt */ DebugMonitor_VECTORn = 12, /*!< 12 Cortex-M4 Debug Monitor Interrupt */ PendSV_VECTORn = 14, /*!< 14 Cortex-M4 Pend SV Interrupt */ SysTick_VECTORn = 15, /*!< 15 Cortex-M4 System Tick Interrupt */ /****** Kinetis 60 specific Interrupt Numbers ************************************/ DMA0_VECTORn , // DMA Channel 0 Transfer Complete DMA1_VECTORn , // DMA Channel 1 Transfer Complete …… …… } VECTORn_t; 枚举各种 中断向量 后面的为 K60 芯片规定的中 编写好中断函数后,使能中断前调用 set_vector_handler 函数来重新设定中断函 数入口即可。假定我们需要设置硬件上访中断的中断服务函数为 HardFault_Handler, 那么在 main 函数里执行下面函数即可。 set_vector_handler(HardFault_VECTORn, HardFault_Handler); 山外论坛 http://vcan123.com 山外メ雲ジ ~ 67 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 安全检查 为了加强程序的健壮性,减少 bug,山外为此而在程序里添加了一些安全措施, 方便在调试阶段就能检查出错误,消灭 bug。 断言 相信现在还存在着很多连断言是什么都不知道的软件工程师,更别说是初学者了。 断言,其实就是一个宏定义: 1. /* 注:山外修改过这里的代码,与官方提供例子是不一样的 */ 2. #ifdef DEBUG 3. #define ASSERT(expr) \ 4. if (!(expr)) \ 5. assert_failed(__FILE__, __LINE__) 6. #else 7. #define ASSERT(expr) 8. #endif 9. 10. //如果断言条件不成立,进入了错误状态,就会打印错误信息和用 LED 来显示状态 11. void assert_failed(char *file, int line) 12. { 13. LED_init(); 14. while (1) 15. { 16. #ifdef DEBUG_PRINT 17. printf(ASSERT_FAILED_STR, file, line); //打印错误信息 18. #endif 19. water_lights(); //用流水灯来指示进入错误状态 20. } 21. } 如果定义了 DEBUG,就会对断言里的条件进行检查,条件不成立就提示错误信 息,进入死循环。因为会进入死循环,很明显,这个断言是用来检测我们的程序错误, 例如传递给函数的变量取值范围是否正确。例如在 ADC 模块里,可以看到很多函数的 开头都有这样一个语句: 1. ASSERT( ((adcn == ADC0) && (ch>=AD8 && ch<=AD18)) || ((adcn == ADC1)&& (ch>=AD4a && ch<=AD17)) ) ; 2. //使用断言检测 ADCn_CHn 是否正常 如果传递错误的参量,就会出错,在串口里可以看到错误的位置: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 68 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 这样在调试阶段,我们就能发现传递给函数的变量是不是超出了范围,在调试阶 段就解决 bug。 关于断言的使用,从 林锐的《高质量 C 编程指南》里提取了一段断言的使用规 则: 程序一般分为 Debug 版本和 Release 版本,Debug 版本用于内部调试,Release 版本发行给用户使用。 断言 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。 【规则 6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误 情况之间的区别,后者是必然存在的并且是一定要作出处理的。 【规则 6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性)。 【建议 6-5-1】在编写函数时,要进行反复的考查,并且自问:“我打算做哪些 假定?”一旦确定了的假定,就要使用断言对假定进行检查。 【建议 6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风 格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则 要使用断言进行报警。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 69 ~ 枚举 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 枚举的使用,本身就是用来限制变量的范围,可以在编译阶段就进行报错,让程 序员可以及时发现错误和修正错误。 山外为此也在程序里使用了很多枚举定义,方便在调试阶段就能检查出错误,消 灭 bug。 例如 ADC.h 文件里 1. typedef enum ADCn //ADC 端口 2. { 3. ADC0, 4. ADC1 5. }ADCn; 如果打开其他网络上共享的工程,你们会发现他们往往都是使用宏定义,但宏定 义是仅仅进行替换操作,不进行安全检测,这就无法保证安全性。对于一个软件工程 师来说,对程序的安全性进行检测,那是一个基本的要求,但可惜很多程序员都没法 做到这点。山外的函数定义里就规定了传递进去的参数是枚举,大大保障了程序的安 全性: 1. void adc_init(ADCn adcn,ADC_Ch ch); 举个例子: 1. adc_init(0,8); //报警告 2. adc_init(ADC0,SE8); //安全通过检查 山外论坛 http://vcan123.com 山外メ雲ジ ~ 70 ~ PORT 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 一座古城,它有多个城门,每个城门可以允许不同人的进出。 如果把 K60/KL26 比作一座古城,那么 PORT 模块就好比城门那样。PORT 模块, 是 K60/KL26 的管脚管理模块,控制每个管脚复用到各个不同的内部模块(GPIO、 UART、I2C 等),还可以配置每个管脚的各种属性(上拉下拉电阻、无源滤波等)。 快速入门:PORT 库使用方法 形参变量的命名及其作用 形参变量 作用 PTXn_e 管脚编号 取值 PTA0、PTA2、……、PTE31 此取值为多种搭配选择,可在下列选项中多选(有的 选项不可同时选择,看标注): IRQ_ZERO(低电平触发), IRQ_RISING(上升沿触发), IRQ_FALLING(下降沿触发), IRQ_EITHER(跳变沿触发), IRQ_ONE(高电平触发), DMA_RISING(上升沿触发), DMA_FALLING(下降沿触发), 这几个最多可 选其中 1 个。 KL26 只有 PTA/PTC/PTD 可选。 DMA_EITHER(跳变沿触发), cfg 管脚属性配置 HDS(输出高驱动能力), KL26 仅以下可 选:PTB0/PTB1/ PTD6/PTD7。 ODO(漏极输出), PF(带无源滤波器), SSR(输出慢变化率), KL26 没此选项 PULLDOWN(下拉电阻), PULLUP(上拉电阻), 这几个最多可 选其中 1 个 山外论坛 http://vcan123.com 山外メ雲ジ ~ 71 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 ALT0(复用功能 0), ALT1(复用功能 1), ALT2(复用功能 2), ALT3(复用功能 3), ALT4(复用功能 4), ALT5(复用功能 5), ALT6(复用功能 6), ALT7(复用功能 7), 山外 这几个最多 可选其中 1 个,不选, 表示 ALT0 常用枚举列表 PTXn_e PTX_e PTn_e 名称 管脚编号 端口模块 管脚的位号 功能说明 函数列表 函数名称 port_init port_init_NoALT 函数功能 PORT 初始化(配置 MUX 复用功能) PORT 初始化(不改变 MUX 复用功能) 宏定义列表 宏定义名词 PORT_FUNC(X,num,func) 功能说明 PORT 中断服务函数里简化代码量的宏定 义。X 为 A~E,num 为 0~31,func 为相 应的中断处理函数。 枚举详解 枚举定义 PTXn_e 1 /*! 枚举管脚编号 */ 2 typedef enum { 3 /* PTA 端口 */ //0~31 4 PTA0, PTA1, PTA2, PTA3, ..., PTA31, 5 6 /* PTB 端口 */ //32~63 …表示省略部分 没显示出来。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 72 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 7 PTB0, PTB1, PTB2, PTB3, ..., PTB31, 8 9 /* PTC 端口 */ 10 PTC0, PTC1, PTC2, PTC3, ..., PTC31, 11 12 /* PTD 端口 */ 13 PTD0, PTD1, PTD2, PTD3, ..., PTD31, 14 15 /* PTE 端口 */ 16 PTE0, PTE1, PTE2, PTE3, ..., PTE31, 17 } PTXn_e; 枚举作用 用来定义管脚编号。很多函数都用此枚举作为形参来控制端口。 山外 枚举定义 1 /*! 枚举端口模块 */ 2 typedef enum { 3 PTA, PTB, PTC, PTD, PTE, 4 5 PTX_MAX, 6 } PTX_e; 枚举作用 用来定义端口模块。 PTX_e 枚举定义 PTn_e 1 /*! 枚举编号 */ 2 typedef enum { 3 PT0 , PT1 , PT2 , PT3 , PT4 , PT5 , PT6 , PT7 , 4 PT8 , PT9 , PT10, PT11, PT12, PT13, PT14, PT15, 5 PT16, PT17, PT18, PT19, PT20, PT21, PT22, PT23, 山外论坛 http://vcan123.com 山外メ雲ジ ~ 73 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 6 PT24, PT25, PT26, PT27, PT28, PT29, PT30, PT31, 7 } PTn_e; 枚举作用 用来定义管脚的位号。 山外 函数详解 函数原型 1 void port_init port_init (PTXn_e , uint32 cfg); //PORT 初始化(配置 MUX 复用功能) 功能说明 PORT 初始化(配置 MUX 复用功能)。 调用例子 1 //初始化 PTA8 管脚,上升沿触发中断,带无源滤波器,复用功能为 GPIO ,上拉电阻 2 port_init (PTA8, IRQ_RISING | PF | ALT1 | PULLUP ); 函数原型 port_init_NoALT 1 void port_init_NoALT (PTXn_e , uint32 cfg); 功能说明 PORT 初始化(不改变 MUX 复用功能)。 //PORT 初始化(不改变 MUX 复用功能) 调用例子 1 //初始化 PTA8 管脚,上升沿触发中断,带无源滤波器,保留原先复用功能,上拉电阻 2 port_init_NoALT (PTA8, IRQ_RISING | PF | PULLUP ); 山外论坛 http://vcan123.com 山外メ雲ジ ~ 74 ~ 宏定义 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 PORT_FUNC 功能说明 PORT_FUNC 是用在 PORT 中断服务函数里,用于简化代码量的宏定义。X 为 A~E,num 为 0~31,func 为相应的中断处理函数。 常规的 PORT 中断服务函数是这样的: 1 void porta_handler(void) 2{ 3 uint8 n = 0; 4 //引脚号 5 //PTA6 6 n = 6; 7 if (PORTA_ISFR & (1 << n)) { 8 PORTA_ISFR = (1 << n); 9 10 /* 以下为用户任务 */ 11 key(); 12 13 /* 以上为用户任务 */ 14 } 15 } 使用此宏定义,可简化为: //PTA6 触发中断 //写 1 清中断标志位 1 void porta_handler(void) 2{ 3 PORT_FUNC(A,6,key); 4} 调用例子举例 看功能说明,此处略。 这样修改起来就方便多了。Key()是自行 编写的 PTA6 中断处理函数,不需要考 虑清中断标志位。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 75 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 PORT 测试例程 山外 K60 PORT 外部中断实验 此例程,我们使用山外 K60 核心板(原野火)上的独立按键,用到 PTD7 管脚。 PORT 模块初始化为内部上拉电阻,那么按键弹起时 PTD7 管脚为高电平,按键按下 时 PTD7 管脚为低电平。 1 void PORTD_IRQHandler(void); 2 void key_handler(void); //PORTD 端口中断服务函数 //按键按下的测试中断复位函数 3 4 /*! 5 * @brief main 函数 6 * @since v5.0 7 * @note 8 9 10 11 测试 port 配置功能,需要接串口来看测试效果 按键一端接 PTD7 ,另一端接地 按键按下时,PTD7 接地 按键没有弹起时,PTD7 浮空,因此需要上拉电阻来把电平拉高 PTD7 就是 K60 核心板的独立按键(可以用杜邦线手动接地代替按键按下) 12 */ 13 void main() 14 { 15 printf("\n*****按键测试*****\n"); 16 port_init(PTD7, ALT1 | IRQ_FALLING | PULLUP ); //初始化 PTD7 管脚,复用功能为 GPIO ,下降沿触发中断,上拉电阻 17 set_vector_handler(PORTD_VECTORn ,PORTD_IRQHandler); 山外论坛 http://vcan123.com 山外メ雲ジ ~ 76 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 //设置 PORTD 的中断服务函数为 PORTD_IRQHandler 18 enable_irq (PORTD_IRQn); //使能 PORTD 中断 山外 19 while(1) 20 { 21 //disable_irq(PORTD_IRQn); 22 } 23 } 对于 KL26, 只有 A 、C、D 端口可触发 IO 中断 和 DMA。其中 C/D 端口 共用相同中断向量号,因此 PORTD_IRQn 需要改为:PORTC_PORTD_IRQn PORTD_VECTORn 需要改为:PORTC_PORTD_VECTORn 24 25 /*! 26 * @brief PORTD 端口中断服务函数 27 * @since v5.0 28 */ 29 void PORTD_IRQHandler(void) 30 { 31 PORT_FUNC(D,7,key_handler); 32 } 33 34 /*! 此语句可以改为: uint32 n = 7; if(PORTD_ISFR & (1 << n)) { PORTD_ISFR = (1 << n); key_handler(); } //PTD7 触发中断 //写 1 清中断标志位 35 * @brief 按键按下的测试中断复位函数 36 * @since v5.0 37 */ 38 void key_handler(void) 39 { 40 printf("\n 按下按键\n"); 41 } 编译下载后,通过串口助手可以看到按键按下时串口会提示按键按下的信息。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 77 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K26 PORT 外部中断实验 KL26 例程与 K60 例程几乎相同,这里我们用 PTC9,即小液晶上的中键。把小液 晶接入到 KL26 小底板。接着插入 USB 转 TTL 模块到串口接口,注意,串口接口是交 叉接线的,即核心板上的 TX 接串口模块的 RX,RX 接串口模块的 TX。 1 void PORTC_PORTD_IRQHandler(void); 2 void key_handler(void); // PORTC 和 PORTD 端口中断服务函数 //按键按下的测试中断服务函数 3 4 /*! 山外论坛 http://vcan123.com 山外メ雲ジ ~ 78 ~ 5 * @brief main 函数 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 6 * @since v5.0 7 * @note 8 9 10 测试 port 配置功能,需要接串口来看测试效果 按键一端接 PTC9 ,另一端接地 (就是小液晶上的中键) 按键按下时,PTC9 接地 按键没有弹起时,PTC9 浮空,因此需要上拉电阻来把电平拉高 11 */ 12 void main() 13 { 14 printf("\n*****按键测试*****\n"); PTC9,即小液晶上的中键。 KL26 支持外部中断的端口有 A/C/D,其中 C/D 共用相同中断号。 15 16 port_init(PTC9, ALT1 | IRQ_FALLING | PULLUP ); //初始化 PTC9 管脚,复用功能为 GPIO ,下降沿触发中断,上拉电阻 17 18 set_vector_handler(PORTC_PORTD_VECTORn ,PORTC_PORTD_IRQHandler); //设置 PORTC 和 PORTD 的中断服务函数为 PORTC_PORTD_IRQHandler 19 enable_irq (PORTC_PORTD_IRQn); //使能 PORTC 和 PORTD 中断 20 21 while(1) KL26 的 C/D 共用相同中断号。 22 { 23 //disable_irq(PORTC_PORTD_IRQn); 24 } 25 } 26 27 /*! 28 * @brief PORTC 和 PORTD 端口中断服务函数 29 * @since v5.0 30 */ 31 void PORTC_PORTD_IRQHandler(void) 32 { 33 34 #if 0 35 // 条件编译,两种方法可供选择 这里宏条件编译,有两种方法 可选,任选其中一种即可。 这里为 0,即选择 #else 下面 的那种方法。 36 uint8 n = 0; //引脚号 37 n = 9; 38 if(PORTC_ISFR & (1 << n)) 39 { //PTC9 触发中断 如果需要改成其他管脚,只 需修改代码中蓝色背景的字 40 PORTC_ISFR = (1 << n); //写 1 清中断标志位 41 山外论坛 http://vcan123.com 山外メ雲ジ ~ 79 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 42 /* 以下为用户任务 */ 43 44 key_handler(); 45 46 /* 以上为用户任务 */ 47 } 48 #else 49 PORT_FUNC(C,9,key_handler); 这种方法,实际上就是封装上 面的代码,以便简化代码。 50 #endif 51 } 52 53 /*! 54 * @brief 按键按下的测试中断服务函数 55 * @since v5.0 56 */ 57 void key_handler(void) 58 { 59 printf("\n 按下按键\n"); 60 } 山外 烧录程序后,按液晶上的中键,可在串口助手上看到实验效果,详情可参考 K60 的 实验效果。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 80 ~ GPIO 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 快速入门:GPIO 库使用方法 形参变量的命名及其作用 形参变量 作用 PTXn_e 管脚编号 GPIO_CFG 输入输出配置 data 数据 取值 PTA0、PTA2、……、PTE31 GPI , GPO, 山外 常用枚举列表 GPIO_CFG 名称 功能说明 管脚输入输出配置 函数列表 gpio_init gpio_ddr gpio_set gpio_turn gpio_get 函数名称 函数功能 初始化 gpio 设置引脚数据方向 设置引脚状态 反转引脚状态 获取 IO 状态 宏定义列表 宏定义名词 PTxn_OUT PTxn_IN PTxn_DDR 功能说明 设置引脚输出电平(这里的 x 要替换成 A~E,n 要替换为 0~31 的数,表示对应的 引脚号) 例如:PTA0_OUT 读取引脚输入电平(这里的 x 要替换成 A~E,n 要替换为 0~31 的数,表示对应的 引脚号) 例如:PTA0_IN 设置引脚输入输出方向(这里的 x 要替换 成 A~E,n 要替换为 0~31 的数,表示对应 的引脚号) 例如:PTA0_DDR 山外论坛 http://vcan123.com 山外メ雲ジ ~ 81 ~ PTxn_T PTx_Bn_OUT PTx_Bn_IN PTx_Bn_DDR PTx_Bn_T PTx_Wn_OUT PTx_Wn_IN PTx_Wn_DDR PTx_Wn_T PTx_OUT PTx_IN 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 反转引脚状态(这里的 x 要替换成 A~E,n 要替换为 0~31 的数,表示对应的引脚 号) 例如:PTA0_T 设置 8 位引脚输出电平(这里的 x 要替换 成 A~E,n 要替换为 0~3 的数,表示对应 B 表示字节 BYTE 的 8 位引脚号) 例如: PTA_B0_OUT 表示 PTA7~ PTA0 PTA_B1_OUT 表示 PTA15~ PTA8 PTA_B2_OUT 表示 PTA23~ PTA16 PTA_B3_OUT 表示 PTA31~ PTA24 设置 8 位引脚输入电平(这里的 x 要替换 成 A~E,n 要替换为 0~3 的数,表示对应 的 8 位引脚号) 设置 8 位引脚输入输出方向(这里的 x 要 替换成 A~E,n 要替换为 0~3 的数,表示 对应的 8 位引脚号) 反转 8 位引脚状态(这里的 x 要替换成 A~E,n 要替换为 0~31 的数,表示对应的 引脚号) W 表示字 WORD 设置 16 位引脚输出电平(这里的 x 要替 换成 A~E,n 要替换为 0~1 的数,表示对 应的 16 位引脚号) 例如: PTA_W0_OUT 表示 PTA15~ PTA0 PTA_W1_OUT 表示 PTA31~ PTA16 设置 16 位引脚输入电平(这里的 x 要替 换成 A~E,n 要替换为 0~1 的数,表示对 应的 16 位引脚号) 设置 16 位引脚输入输出方向(这里的 x 要替换成 A~E,n 要替换为 0~1 的数,表 示对应的 16 位引脚号) 反转 16 位引脚状态(这里的 x 要替换成 A~E,n 要替换为 0~31 的数,表示对应的 引脚号) 设置 32 位引脚输出电平(这里的 x 要替 换成 A~E) 设置 32 位引脚输入电平(这里的 x 要替 换成 A~E) 山外论坛 http://vcan123.com 山外メ雲ジ ~ 82 ~ PTx_DDR PTx_T GPIO_SET GPIO_TURN GPIO_GET GPIO_DDR GPIO_SET_NBIT GPIO_GET_NBIT GPIO_DDR_NBIT GPIO_T_NBIT 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 设置 32 位引脚输入输出方向(这里的 x 要替换成 A~E) 反转 32 位引脚状态(这里的 x 要替换成 A~E) 设置引脚状态 翻转引脚状态 获取 IO 状态 输入输出状态 参数 PTxn 只能是 宏 定义,不能是 变量 设置 n 位引脚输出电平 设置 n 位引脚输入电平 设置 n 位引脚输入输出方向 反转 n 位引脚状态 枚举详解 枚举定义 1 typedef enum GPIO_CFG 2{ 3 //这里的值不能改!!! 4 GPI = 0, 5 GPO = 1, 6 } GPIO_CFG; GPIO_CFG //定义管脚输入方向 //定义管脚输出方向 GPIOx_PDDRn 里,0 表示输入,1 表示输出 枚举作用 用来定义 GPIO 管脚输入输出的配置。 函数详解 函数原型 gpio_init 输入模式下,data 形参 是无效的 1 void gpio_init (PTXn_e, GPIO_CFG, uint8 data); //初始化 gpio 功能说明 初始化 gpio,设置端口的输入输出方向,输出数据。初始化后,才能对 IO 口进 行读取或电平设置。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 83 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 调用例子 1 gpio_init (PTA8, GPI,0); //初始化 PTA8 管脚为输入 函数原型 gpio_ddr 1 void gpio_ddr (PTXn_e, GPIO_CFG); 功能说明 设置引脚数据方向。 //设置引脚数据方向 调用例子 1 gpio_ddr (PTA8, GPI); //设置 PTA8 管脚为输入 函数原型 1 void gpio_set (PTXn_e, 功能说明 设置引脚状态。 gpio_set uint8 data); //设置引脚状态 调用例子 1 gpio_set (PTA8, 1); // PTA8 管脚 输出 1 函数原型 1 void gpio_turn (PTXn_e); 功能说明 翻转 IO 口电平 gpio_turn //反转引脚状态 调用例子 1 gpio_turn (PTA8); // PTA8 管脚 输出 反转 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 84 ~ 函数原型 攻城略地之一天攻破 K60/KL26 —— 松飞科技 gpio_get 1 uint8 gpio_get (PTXn_e); 功能说明 读取引脚状态 //读取引脚状态 函数返回 返回 1bit 的端口电平状态。 调用例子 1 uint8 pta8_data = gpio_get (PTA8); // 获取 PTA8 管脚 输入电平 山外 宏定义 PTxn_OUT 功能说明 设置电平输出。这里的 x 要替换成 A~E 的端口号 , n 要替换为 0~31 的数,表 示对应的引脚号,例如 PTA0_OUT 调用例子举例 1 PTA0_OUT = 1; //PTA0 输出高电平 2 PTB16_OUT = 1; //PTB16 输出高电平 3 PTE28_OUT = 0; //PTE28 输出低电平 PTxn_IN 功能说明 读取电平输入。这里的 x 要替换成 A~E 的端口号 , n 要替换为 0~31 的数,表 示对应的引脚号,例如 PTA0_IN 调用例子举例 1 data=PTA0_IN; //读取 PTA0 输入电平 2 data=PTB16_IN; //读取 PTB16 输入电平 3 data=PTE28_IN; //读取 PTE28 输入电平 山外论坛 http://vcan123.com 山外メ雲ジ ~ 85 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 PTxn_DDR 功能说明 设置引脚输入输出方向。这个是通用显示名字而已,实际上这里的 x 要替换成 A~E,n 要替换为 0~31 的数,表示对应的引脚号。例如:PT A0_DDR 调用例子举例 1 PTA0_DDR=0; //设置 PTA0 为输入 2 PTB16_DDR=1; //设置 PTB16 为输出 3 PTE28_DDR=0; //设置 PTE28 为输入 PTxn_T 功能说明 反转引脚状态, 1 表示反转, 0 表示保持不变。。这里的 x 要替换成 A~E,n 要替 换为 0~31 的数,表示对应的引脚号。例如:PT A0_T 调用例子举例 1 PTA0_T=0; //保持 PTA0 引脚输出电平状态 2 PTB16_T=1; //反转 PTB16 引脚输出电平状态 3 PTE28_T=0; //保持 PTE28 引脚输出电平状态 PTx_Bn_OUT 功能说明 设置 8 位引脚输出电平(这里的 x 要替换成 A~E,n 要替换为 0~3 的数,表示对 应的 8 位引脚号) 调用例子举例 1 PTA_B0_OUT=0x12 //表示 PTA7~ PTA0 8 位输出 0x12 2 PTA_B1_OUT=0x12 //表示 PTA15~ PTA8 8 位输出 0x12 3 PTA_B2_OUT=0x12 //表示 PTA23~ PTA16 8 位输出 0x12 4 PTA_B3_OUT=0x12 //表示 PTA31~ PTA24 8 位输出 0x12 山外论坛 http://vcan123.com 山外メ雲ジ ~ 86 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 PTx_Bn_IN 功能说明 读取 8 位引脚输入电平(这里的 x 要替换成 A~E,n 要替换为 0~3 的数,表示对 应的 8 位引脚号) 调用例子举例 1 char data; 2 data = PTA_B0_IN //表示 PTA7~ PTA0 8 位数据输入到 data 变量 3 data = PTA_B1_IN //表示 PTA15~ PTA8 8 位数据输入到 data 变量 4 data = PTA_B2_IN //表示 PTA23~ PTA16 8 位数据输入到 data 变量 5 data = PTA_B3_IN //表示 PTA31~ PTA24 8 位数据输入到 data 变量 PTx_Bn_DDR 功能说明 设置 8 位引脚输入输出方向(这里的 x 要替换成 A~E,n 要替换为 0~3 的数,表 示对应的 8 位引脚号) 调用例子举例 1 PTA_B0_DDR=0xFF; // 表示 PTA7~ PTA0 8 位设置输入输出方向为输出 2 PTA_B1_DDR=0xFF; // 表示 PTA15~ PTA8 8 位设置输入输出方向为输出 3 PTA_B2_DDR=0xFF; // 表示 PTA23~ PTA16 8 位设置输入输出方向为输出 4 PTA_B3_DDR=0xFF; // 表示 PTA31~ PTA24 8 位设置输入输出方向为输出 对应位为 1 表示输 出,0 表示输入。 PTx_Bn_T 功能说明 反转 8 位引脚状态,对应位为 1 表示反转,对应位为 0 表示保持不变。(这里的 x 要替换成 A~E,n 要替换为 0~3 的数,表示对应的 8 位引脚号) 调用例子举例 对应位为 1 表示反转, 0 表示保持不变。 1 PTA_B0_T=0xFF; // 表示 PTA7~ PTA0 8 位管脚的输出电平反转 2 PTA_B1_T=0xFF; // 表示 PTA15~ PTA8 8 位管脚的输出电平反转 3 PTA_B2_T=0xFF; // 表示 PTA23~ PTA16 8 位管脚的输出电平反转 4 PTA_B3_T=0xFF; // 表示 PTA31~ PTA24 8 位管脚的输出电平反转 山外论坛 http://vcan123.com 山外メ雲ジ ~ 87 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 PTx_Wn_OUT 功能说明 设置 16 位引脚输出电平(这里的 x 要替换成 A~E,n 要替换为 0~1 的数,表示对 应的 16 位引脚号) 调用例子举例 1 PTA_W0_OUT=0xFF00; //表示 PTA15~ PTA0 输出 0xFF00 2 PTA_W1_OUT=0xFF00; //表示 PTA31~ PTA16 输出 0xFF00 PTx_Wn_IN 功能说明 读取 16 位引脚输入电平(这里的 x 要替换成 A~E,n 要替换为 0~1 的数,表示对 应的 16 位引脚号) 调用例子举例 1 uint16 data = PTA_W0_IN; //表示 PTA15~ PTA0 输入数据到 data 变量 2 uint16 data = PTA_W1_IN; //表示 PTA31~ PTA16 输入数据到 data 变量 PTx_Wn_DDR 功能说明 设置 16 位引脚输入输出方向(这里的 x 要替换成 A~E,n 要替换为 0~1 的数,表 示对应的 16 位引脚号) 调用例子举例 1 PTA_W0_DDR=0xFFFF; // 表示 PTA15~ PTA0 2 PTA_W1_DDR=0xFFFF; // 表示 PTA31~ PTA16 16 位设置输入输出方向为输出 16 位设置输入输出方向为输出 PTx_Wn_T 功能说明 反转 16 位引脚状态,对应位为 1 表示反转,对应位为 0 表示保持不变。(这里 的 x 要替换成 A~E,n 要替换为 0~3 的数,表示对应的 16 位引脚号) 山外论坛 http://vcan123.com 山外メ雲ジ ~ 88 ~ 调用例子举例 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 1 PTA_W0_T=0xFFFF; // 表示 PTA15~ PTA0 2 PTA_W1_T=0xFFFF; // 表示 PTA31~ PTA16 16 位管脚的输出电平反转 16 位管脚的输出电平反转 对应位为 1 表示反转, 0 表示保持不变。 功能说明 设置 32 位引脚输出电平 PTx_OUT 调用例子举例 1 PTA_OUT=0xFFFF0000; // 表示 PTA31~ PTA16 输出 1,PTA15~ PTA0 输出 0 功能说明 读取 16 位引脚输入电平 PTx_IN 调用例子举例 1 uint32 data = PTA_IN; //表示 PTA31~ PTA0 输入数据到 data 变量 PTx_DDR 功能说明 设置 32 位引脚输入输出方向 对应位为 1 表示输 出,0 表示输入。 调用例子举例 1 PTA_DDR=0xFFFF0000; // 表示 PTA15~ PTA0 16 位设置输入输出方向为输入 ,PTA31~ PTA16 16 位设置输入输出方向为输出 PTx_T 功能说明 反转 32 位引脚状态,对应位为 1 表示反转,对应位为 0 表示保持不变。 调用例子举例 1 PTA_T=0xFFFF0000; // 表示 PTA15~ PTA0 ,PTA31~ PTA16 16 位管脚的输出电平保持不变 16 位管脚的输出电平反转 山外论坛 http://vcan123.com 山外メ雲ジ ~ 89 ~ 原型 1 GPIO_SET(PTxn,data) 攻城略地之一天攻破 K60/KL26 —— 松飞科技 GPIO_SET 功能说明 设置引脚输出电平 传递参数 参考形参变量的命名及其作用。 注意:PTXn_e 只能是宏定义,不能是变量 调用例子举例 1 GPIO_SET(PTA8,1); //PTA8 输出 1,等效于 PTA8_OUT = 1; 山外 GPIO_TURN 原型 1 GPIO_TURN(PTxn) 功能说明 反转指定管脚的输出电平 传递参数 参考形参变量的命名及其作用。 注意:PTXn_e 只能是宏定义,不能是变量 调用例子举例 1 GPIO_TURN(PTA8); //反转 PTA8 的输出电平,等效于 PTA8_T = 1; 原型 1 GPIO_GET(PTxn) 功能说明 读取引脚输入状态 GPIO_GET 山外论坛 http://vcan123.com 山外メ雲ジ ~ 90 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 传递参数 参考形参变量的命名及其作用。 注意:PTXn_e 只能是宏定义,不能是变量 调用例子举例 1 data = GPIO_GET(PTA8); //读取 PTA8 输入电平到变量 data 里 原型 1 GPIO_DDR(PTxn,ddr) 功能说明 设置引脚输入输出方向 GPIO_DDR 传递参数 ddr 表示输入输出方向,1 时为输出,0 时为输入。 注意:PTXn_e 只能是宏定义,不能是变量 调用例子举例 1 GPIO_DDR(PTA8,0); //设置 PTA8 为输入 GPIO_SET_NBIT 原型 1 GPIO_SET_NBIT(NBIT,PTxn,data) 功能说明 设置 NBIT 位 (最低位为 PTxn)的输出电平为 data 传递参数 NBIT 位数 PTxn 最低位的管脚号(可以是宏定义和变量) data 电平状态 调用例子举例 1 GPIO_SET_NBIT(4,PTA8,9); //PTA11~PTA8 输出数据为 9 山外论坛 http://vcan123.com 山外メ雲ジ 山外 ~ 91 ~ 2 GPIO_SET_NBIT(2,PTA8,9); 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 //PTA9 ~PTA8 输出数据为 1(9 超过 2bit,仅低位数据有效) 原型 1 GPIO_GET_NBIT(NBIT,PTxn) GPIO_GET_NBIT 功能说明 读取 NBIT 位 (最低位为 PTxn)引脚输入状态。 传递参数 NBIT 位数 PTxn 最低位的管脚号(可以是宏定义和变量) data 电平状态 展开举例 1 data = GPIO_GET_NBIT(4,PTA8); //读取 PTA11~PTA8,共 4bit 输入电平到变量 data 里 GPIO_DDR_NBIT 原型 1 GPIO_DDR_NBIT(NBIT,PTxn,ddr) 功能说明 设置 NBIT 位 (最低位为 PTxn)引脚输入输出方向 传递参数 NBIT 位数 PTxn 最低位的管脚号(可以是宏定义和变量) ddr 输入输出方向(1 为输出,0 为输入) 调用例子举例 1 GPIO_DDR_NBIT(4,PTA8,9); //PTA11,PTA8 为输出;PTA10,PTA9 为输入 山外论坛 http://vcan123.com 山外メ雲ジ ~ 92 ~ 原型 攻城略地之一天攻破 K60/KL26 —— 松飞科技 GPIO_T_NBIT 1 GPIO_T_NBIT(NBIT,PTxn,data) 功能说明 反转 NBIT 位 (最低位为 PTxn)管脚的输出电平 山外 传递参数 NBIT 位数 PTxn 最低位的管脚号(可以是宏定义和变量) data 对应位为 1 表示反转电平状态,0 为保持不变。 调用例子举例 1 GPIO_T_NBIT(4,PTA8,9); //反转 PTA11,PTA8 的输出电平;保持 PTA10,PTA9 为输出电平不变 GPIO 测试例程 51 编程风格的 GPIO 实验输出测试 此例程,我们使用山外 K60 核心板(原野火)上的 LED0、LED1,用到 PTB20、 PTB21 管脚。gpio 模块初始化为输出,然后调用 PTxn_OUT、PTxn_T 宏来控制 IO 输 出。 注:此处蓝光 LED1 即本文所说的 LED0, 蓝光 LED2 即本文所说的 LED1 1 /*! 山外论坛 http://vcan123.com 山外メ雲ジ ~ 93 ~ 2 * @brief main 函数 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 3 * @since v5.2 4 * @note 51 编程风格的 GPIO 实验输出测试 5 6 山外 K60 核心板, PTB20、PTB21 对应于 LED0、LED1,低电平亮,高电平灭 7 8 9 */ 10 void main() 实验效果:LED0/LED1 同时亮灭闪烁 gpio_init PTxn_OUT PTxn_T 编程思路 初始化引脚 设置引脚输出数据 反转引脚输出数据 11 { 12 gpio_init(PTB20,GPO,1); 13 gpio_init(PTB21,GPO,1); //初始化 LED0 ,灭 //初始化 LED1 ,灭 14 15 while(1) 16 { 如果需要修改成其他管脚, 可修改黄色背景的字。 17 PTB20_OUT = 0; 18 PTB21_T = 1; //PTB20 输出 0,即 LED0 亮 //PTB21 输出反转,即 LED1 由灭变亮 19 20 DELAY_MS(500); //延时 500ms 21 22 PTB20_OUT = 1; //PTB20 输出 1,即 LED0 灭 23 PTB21_T = 1; //PTB21 输出反转,即 LED1 由亮变灭 24 25 DELAY_MS(500); //延时 500ms 26 } 27 } 山外 KL26 核心板(原野火)上的 LED0、LED1,用到 PTD4、PTD5 管脚,代码 几乎 相同,只需要修改一下相应的管脚,这里不再重复了。 51 编程风格的 GPIO 实验输入输出测试 此例程,我们使用山外 K60 核心板(原野火)上的 LED0、独立按键,用到 PTB20、PTD7 管脚。PTB20 初始化为输出,PTD7 初始化为带内部上拉电阻的输入, 然后调用 PTxn_OUT、PTxn_IN 宏来控制 IO 输入输出。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 94 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 1 /*! 2 * @brief 3 * @since 4 * @note 5 main 函数 v5.2 51 编程风格的 GPIO 实验输入输出测试 编程思路 gpio_init 初始化引脚 port_init_NoALT 不改变复用,修改端口属性 PTxn_OUT 设置引脚的输出数据 PTxn_IN 读取引脚的输入数据 6 山外 K60 核心板, PTB20 对应于 LED0,低电平亮,高电平灭 7 PTD7 对应于独立按键,需要内部上拉,按下时为低电平 8 9 实验效果:独立按键按下时,LED0 亮;独立按键弹起时,LED 灭 10 */ 11 void main() 12 { 13 gpio_init(PTB20,GPO,1); //初始化 LED0 ,灭 14 15 gpio_init(PTD7,GPI,0); //初始化核心板独立按键,内部上拉电阻 16 port_init_NoALT(PTD7,PULLUP); 17 18 while(1) 如果需要修改成其他管脚, 可修改黄色背景的字。 19 { 20 if(PTD7_IN == 1) //按键弹起 21 { 22 PTB20_OUT = 1; //PTB20 输出 1,即 LED0 灭 23 } 24 else //按键按下 25 { 26 PTB20_OUT = 0; //PTB20 输出 0,即 LED0 亮 27 } 山外论坛 http://vcan123.com 山外メ雲ジ ~ 95 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 28 //实际上 while 循环可简化为:PTB20_OUT = PTD7_IN; 29 } 30 } 山外 山外 KL26 核心板(原野火)上的 LED0 对应 PTD4,小液晶中键对应于 PTC9, 代码几乎 相同,只需要修改一下相应的管脚,这里不再重复了。 GPIO 函数调用测试 此例程,我们使用山外 K60 核心板(原野火)上的 LED0,用到 PTB20 管脚。 PTB20 初始化为输出,调用 gpio_set 来控制 IO 输出电平,从而闪烁 LED0。 1 /*! 2 * @brief main 函数 3 * @since v5.2 4 * @note GPIO 简单函数调用实验 5 6 山外 K60 核心板, PTB20 对应于 LED0,低电平亮,高电平灭 7 8 9 */ 10 void main() 实验效果:LED0 亮灭闪烁 gpio_init gpio_set 编程思路 初始化引脚 设置引脚的输出数据 11 { 12 gpio_init(PTB20,GPO,1); //初始化 LED0 ,灭 13 while(1) 14 { 如果需要修改成其他管脚, 可修改黄色背景的字。 15 gpio_set(PTB20,0); 16 DELAY_MS(500); 17 gpio_set(PTB20,1); 18 DELAY_MS(500); //PTB20 输出 0,即 LED0 亮 //延时 500ms //PTB20 输出 1,即 LED0 灭 //延时 500ms 19 } 20 } 山外 KL26 核心板(原野火)上的 LED0 对应 PTD4,代码几乎 相同,只需要修改 一下相应的管脚,这里不再重复了。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 96 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 GPIO 并行输出实验 此例程,我们使用山外 K60 核心板(原野火)上的 LED0~LED3,用到 山外 PTB20~PTB23 管脚。全部初始化为输出,调用 GPIO_SET_NBIT 来并行控制 IO 输出 电平,从而闪烁各个 LED。 1 /*! 2 * @brief main 函数 3 * @since v5.2 4 * @note GPIO 并行输出实验 5 6 山外 K60 核心板, PTB20~PTB23 对应于 LED0~LED3,低电平亮,高电平灭 7 8 实验效果:LED0/LED3 同时亮灭,LED1/LED2 同时亮灭,4 则分别闪烁 9 */ 10 void main() 11 { 编程思路 gpio_init 初始化引脚 GPIO_SET_NBIT 设置引脚的输出数据 12 //IO 口需要一个个初始化 13 gpio_init(PTB20,GPO,1); 14 gpio_init(PTB21,GPO,1); 15 gpio_init(PTB22,GPO,1); 16 gpio_init(PTB23,GPO,1); //初始化 LED0 ,灭 //初始化 LED1 ,灭 //初始化 LED2 ,灭 //初始化 LED3 ,灭 17 18 while(1) 19 { 20 GPIO_SET_NBIT(4,PTB20,9); 如果需要修改成其他管脚, 可修改黄色背景的字。 //PTB20/PTB23 输出 1,即 LED0/LED3 灭;PTB21/PTB22 输出 0,即 LED1/LED2 亮 21 DELAY_MS(500); //延时 500ms 22 GPIO_SET_NBIT(4,PTB20,6); //PTB20/PTB23 输出 0,即 LED0/LED3 亮;PTB21/PTB22 输出 1,即 LED1/LED2 灭 23 DELAY_MS(500); //延时 500ms 24 } 25 } 山外 KL26 核心板(原野火)上的 LED0~LED3 分别对应于 PTD4~ PTD7,代码 几乎 相同,只需要修改一下相应的管脚,这里不再重复了。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 97 ~ VCAN_LED 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 快速入门:LED 库使用方法 形参变量的命名及其作用 形参变量 作用 LEDn LED 号 LED_status LED 状态 取值 LED0, LED1, LED2, LED3, LED_MAX LED_ON , LED_OFF 常用枚举列表 LEDn LED_status 名称 LED 号 LED 状态 功能说明 函数列表 led_init led led_turn 函数名称 初始化 LED 翻转 LED 状态 设置 LED 亮灭 函数功能 枚举详解 枚举定义 1 /*! 枚举 LED 编号 */ 2 typedef enum 3{ 4 LED0, 5 LED1, 6 LED2, 7 LED3, 8 LED_MAX, 9 }LED_e; LEDn LED_MAX 不是 LED 编号,而是 LED 数量,此处 LED_MAX 的值为 4,表示 有 4 个 LED 灯。 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 98 ~ 枚举作用 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 对 LED 进行编号,编号所对应的 I/O 管脚用数组存储,通过查表即可快速得到 LED 所对应的 I/O: 1 /* 2 * 定义 LED 编号对应的管脚 3 */ 此处是 K60 的 LED 管脚定义, KL26 可换成 PTD4~PTD7。 4 PTXn_e LED_PTxn[LED_MAX] = {PTB20,PTB21,PTB22,PTB23}; 枚举定义 1 /*! 枚举 LED 亮灭状态 */ 2 typedef enum LED_status 3{ 4 LED_ON = 0, 5 LED_OFF = 1 6 }LED_status; LED_status //灯亮(对应低电平) //灯暗(对应高电平) 枚举作用 用来定义 LED 亮暗的高低电平 函数详解 函数原型 led_init 1 void led_init(LED_e); //初始化 LED 端口 功能说明 初始化 LED。如果 LED_e 的值不小于 LED_MAX,则初始化全部的 LED。 函数原型 1 void led(LED_e,LED_status); led //设置 LED 灯亮灭 山外论坛 http://vcan123.com 山外メ雲ジ ~ 99 ~ 功能说明 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 设置 LED 亮灭。如果 LED_e 的值不小于 LED_MAX,则设置全部的 LED 亮灭。 调用例子 1 led(LED1,LED_ON); // 设置 LED1 亮 函数原型 led_turn 1 void led_turn(LED_e); //设置 LED 灯亮灭反转 功能说明 翻转 LED 状态。如果 LED_e 的值不小于 LED_MAX,则翻转全部的 LED 状态。 调用例子 1 led_turn(LED1); //翻转 LED1 状态,假设 LED1 本来亮着,则变暗 LED 测试例程 LED 闪烁测试例程 此例程,我们使用山外 K60/KL26 核心板(原野火)上的 LED0、LED1,由于 LED 的管脚用数组存储起来,我们直接调用 LED 的函数,不再需要关注 LED 的管脚是 什么。初始化 LED0/LED1,调用 led 和 led_turn 函数来控制 LED。 K60 和 KL26,此处代码都是一样的: 1 /*! 2 * @brief main 函数 3 * @since v5.0 4 * @note 5 6 */ 7 void main() 测试 LED 功能是否正常 看到的效果是 LED0 和 LED1 同时亮灭闪烁 led_init led led_turn 8{ 9 led_init(LED0); 10 led_init(LED1); //初始化 LED0 //初始化 LED1 山外论坛 http://vcan123.com 编程思路 初始化 LED 设置 LED 的亮暗 翻转 LED 的亮暗 山外メ雲ジ ~ 100 ~ 11 12 13 14 15 16 17 18 19 20 21 22 23 24 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 while(1) { led(LED0, LED_ON); led_turn(LED1); //LED0 亮 //LED1 翻转 DELAY_MS(500); //延时 500ms led(LED0, LED_OFF); led_turn(LED1); //LED0 灭 //LED1 翻转 DELAY_MS(500); } //延时 500ms 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 101 ~ VCAN_KEY 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 快速入门:KEY 库使用方法 形参变量的命名及其作用 形参变量 作用 KEY_U, //上 KEY_D, //下 取值 山外 KEY_L, //左 KEY_R, //右 KEY_e 按键号 KEY_A, //取消 KEY_B, //选择 KEY_START, //开始 KEY_STOP, //停止 KEY_STATUS_e 按键状态 KEY_MAX, KEY_DOWN = 0, KEY_UP = 1, //按键按下时对应电平 //按键弹起时对应电平 KEY_MSG_t KEY_HOLD, //长按按键 按 键 消 息 结 构 包含 KEY_e 和 KEY_STATUS_e 体 常用枚举列表 名称 KEY_e KEY_STATUS_e 按键号 按键状态 功能说明 常用结构体列表 KEY_MSG_t 名称 功能说明 按键消息结构体 函数列表 函数名称 山外论坛 http://vcan123.com 函数功能 山外メ雲ジ ~ 102 ~ key_init key_get key_check get_key_msg key_IRQHandler 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 KEY 初始化函数(key 小于 KEY_MAX 时初 始化 对应端口,否则初始化全部端口) 检测 key 状态(不带延时消抖) 检测 key 状态(带延时消抖) 获取按键消息,返回 1 表示有按键消息, 定时扫描按 键,必须初始 化 全部按键。 0 为无按键消息 需要定时扫描的中断服务函数(定时时间 为 10ms) 枚举详解 枚举定义 1 //按键端口的枚举 2 typedef enum 3{ 4 KEY_U, //上 5 KEY_D, //下 6 7 KEY_L, //左 8 KEY_R, //右 9 10 KEY_A, //取消 11 KEY_B, //选择 12 13 KEY_START, //开始 14 KEY_STOP, //停止 15 16 KEY_MAX, 17 } KEY_e; KEY_e KEY_MAX 不是按键编号,而是按键数 量,此处 KEY_MAX 的值为 8,表示有 8 个按键。 枚举作用 对按键进行编号,编号所对应的 I/O 管脚用数组存储,通过查表即可快速得到按键 所对应的 I/O: 1 /* 2 * 定义 KEY 编号对应的管脚 假如使用定时扫描按键的方法,但又 不想使用这么多按键,可删掉对应按 键编号和按键管脚。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 103 ~ 3 */ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 4 PTXn_e KEY_PTxn[KEY_MAX] = {PTD10, PTD14, PTD11, PTD12, PTD7, PTD13, PTC14, PTC15}; 枚举定义 1 typedef enum 2{ 3 KEY_DOWN = 0, 4 KEY_UP = 1, 5 6 KEY_HOLD, 7 8 } KEY_STATUS_e; KEY_STATUS_e //按键按下时对应电平 //按键弹起时对应电平 //长按按键 定时扫描按键的方法支持长按识别。 枚举作用 用来定义按键的状态。用于 定时按键扫描时,可识别长按按键。 结构体详解 枚举定义 1 //按键消息结构体 2 typedef struct 3{ 4 KEY_e 5 KEY_STATUS_e 6 } KEY_MSG_t; key; status; KEY_MSG_t //按键编号 //按键状态 定时按键扫描时用,按键消息缓 冲区存放的每个元素就是按键消 息结构体。 枚举作用 定时按键扫描时用存放按键消息的结构,获取消息时会返回此结构的按键消息。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 104 ~ 函数详解 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 函数原型 1 void key_init key_init(KEY_e key); 功能说明 初始化按键函数(key 小于 KEY_MAX 时初始化 对应端口,否则初始化全部端口)。 调用例子 1 KEY_init (KEY_U); //初始化 KEY_U 函数原型 key_get 1 KEY_STATUS_e key_get(KEY_e key); 功能说明 检测 key 状态(带延时消抖)。 调用例子 1 if(key_get(KEY_U) == KEY_DOWN) 2{ 3 printf("\n 按键按下") 4} 函数原型 key_check 1 KEY_STATUS_e key_check(KEY_e key); 功能说明 检测 key 状态(带延时消抖)。 调用例子 1 if(key_check(KEY_U) == KEY_DOWN) 2{ 3 printf("\n 按键按下") 4} 山外论坛 http://vcan123.com 山外メ雲ジ ~ 105 ~ 函数原型 攻城略地之一天攻破 K60/KL26 —— 松飞科技 get_key_msg 山外 1 uint8 get_key_msg(KEY_MSG_t *keymsg); 功能说明 获取按键消息,返回 1 表示有按键消息,0 为无按键消息。 注意,此函数用于定时按键扫描,需要把 key_IRQHandler 函数放入定时中断函 数里运行才可正常返回按键消息。 调用例子 1 KEY_MSG_t keymsg; 2 if(get_key_msg(&keymsg) == 1) 3{ 4 printf("\n 按下按键 KEY%d,类型为%d(0 为按下,1 为弹起,2 为长按)",keymsg.key,keymsg.status); 5} 函数原型 key_IRQHandler 1 void key_IRQHandler(void); 功能说明 需要定时扫描的中断服务函数,推荐定时时间单位为 10ms。 此函数 配合 get_key_msg 函数来使用。 KEY 测试例程 按键延时消抖测试 此例程,我们使用山外 K60 核心板(原野火)上的独立按键,调用 key_init 初始 化按键,key_check 函数查询按键状态,通过串口打印按键消息。 1 /*! 2 * @brief 3 * @since 4 * @note 5 */ main 函数 v5.0 测试 KEY 循环扫描 山外论坛 http://vcan123.com 山外メ雲ジ ~ 106 ~ 6 void main() 7{ 8 key_init(KEY_A); 攻城略地之一天攻破 K60/KL26 —— 松飞科技 KL26 核心板上没独立按键,可 用 KEY_B,即液晶上的中键。 9 10 printf("\n******* GPIO 查询扫描 按键测试 *******"); 11 12 while(1) 13 { 14 if(key_check(KEY_A) == KEY_DOWN) //检测 key 状态(带延时消抖) 15 { 16 printf("\n 按键按下"); //通过串口助手查看,提示按键按下 17 18 DELAY_MS(500); 19 //调整这里的时间,会发现,时间越长, //快速双击,就没法识别第二次采集 20 } 21 } 22 } 山外 电脑连接到串口,通过串口助手可看到实验现象。可以发现,DELAY_MS 的延时 时间增大,在快速双击的情况下,会没法识别到第二次采集,这就是查询按键扫描的 缺点(可用定时按键扫描来改善)。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 107 ~ 定时按键测试 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 此例程,我们使用山外 K60 调试器(原野火)上的全部按键,调用 key_init 初始 化按键,get_key_msg 函数查询按键消息,通过串口打印按键消息。 由于需要定时执行 key_IRQHandler 函数,因此这里需要用到定时器,定时器的 用法可以先不用管,等后续学习中用到再学。 1 /*! 2 * @brief LPTMR 定时执行的中断服务函数 3 * @since v5.0 4 * @note 测试 KEY 定时扫描 5 */ 6 void lptmr_hander(void) 7{ 8 LPTMR0_CSR |= LPTMR_CSR_TCF_MASK; //清除 LPT 比较标志位 9 10 //下面由用户添加实现代码 11 key_IRQHandler(); //把按键扫描程序加入到定时中断服务函数里,定时执行 12 13 } 需要定时执行此函数 14 15 char * keyname[KEY_MAX]={"KEY_U","KEY_D","KEY_L","KEY_R", 16 "KEY_A","KEY_B","KEY_START","KEY_START"}; 17 18 char * keystatus[3]={"按下","弹起","长按"}; 19 20 /*! 这里是为了串口显示方便而数组存 储相应按键的字符串。 21 * @brief main 函数 22 * @since v5.0 23 * @note 测试 KEY 定时扫描,需要通过串口查看结果,串口 波特率 为 VCAN_BAUD 24 */ 25 void main() 26 { 27 KEY_MSG_t keymsg; 28 29 printf("\n******* GPIO 按键定时测试 *******"); 30 31 key_init(KEY_MAX); 定时按键扫描,必须 初始化全部按 键。 32 山外论坛 http://vcan123.com 山外メ雲ジ ~ 108 ~ 33 34 35 36 37 38 39 40 41 42 43 44 45 46 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 lptmr_timing_ms(10); // LPTMR 定时 10ms set_vector_handler(LPTMR_VECTORn,lptmr_hander); // 设置中断服务函数到中断向量表里 enable_irq(LPTMR_IRQn); // 使能 LPTMR 中断 山外 while(1) { 这部分是定时中断的配置代码,可 先不管,知道是定时 10ms 执行 lptmr_hander 函数即可。 while(get_key_msg(&keymsg) == 1) { printf("\n 按键%s%s",keyname[keymsg.key],keystatus[keymsg.status]); } DELAY_MS(500); } //可以 调 延时时间,除非 FIFO 满溢出,不然不会出现漏识别按键 可直接使用调试器上的按键,或者使用小液晶上的五轴按键,如下所示: 烧写例程后,在串口调试助手里可以看到按键按下时会打印各种按键消息,可以 看到支持按键长按和按键弹起的动作。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 109 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 留意代码里 get_key_msg 函数,用的是 while 而不是 if 来判断,目的是按键消息 往往有多个,需要一次性处理好全部的按键消息,避免把按键消息推到很久才处理, 这样就失去了实时性。 定时扫描的好处在于主循环有大任务处理时也可及时扫描按键,如果增大延时函 数 DELAY_MS 的延时时间,经过测试发现:不会因此出现按键漏识别的情况出现。 有没有试过:手机出现卡死的时候,一般会多按几次按键,突然发现画面跳变几 次。其实就是扫描到有按键,把按键消息缓存起来,由于卡死导致没及时处理按键 消息,等不卡死时却由于有按键消息而导致画面跳变几次。把 DELAY_MS 函数的延 时时间改大,例如 10 秒,就会出现类似的情况。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 110 ~ UART 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 快速入门:UART 库使用方法 其他管脚取值,在 形参变量的命名及其作用 app/inc/port_cfg.h 文 件里配置。 形参变量 作用 取值 UARTn_e 串口端口号 //初始化默认配置 --TXD-- --RXD-- K60 的取值范围 UART0, // PTD7 PTD6 UART1, // PTC4 PTC3 UART2, // PTD3 PTD2 UART3, // PTC17 PTC16 UART4, // PTC15 PTC14 UART5 // PTE8 KL26 的取值范围 PTE9 UART0, // PTA14 PTA15 UART1, // PTC4 PTC3 UART2, // PTD3 PTD2 baud 波特率 如 9600、19200、56000、115200 等(不推荐太大) ch 字符 str 字符串 buff 缓冲区 len 长度 常用枚举列表 UARTn_e 名称 串口端口号 功能说明 函数列表 函数名称 uart_init uart_getchar uart_querychar uart_querystr uart_querybuff uart_query uart_putchar uart_putbuff 函数功能 初始化串口 等待接收 1 个字节 查询接收 1 个字符 查询接收字符串 查询接收 buff 查询是否接收到一个字节 发送 1 个字节 发送 n 个字节 山外论坛 http://vcan123.com 山外メ雲ジ ~ 111 ~ uart_putstr uart_rx_irq_en uart_rx_irq_dis uart_tx_irq_en uart_tx_irq_dis uart_txc_irq_en uart_txc_irq_dis 攻城略地之一天攻破 K60/KL26 —— 松飞科技 发送字符串 开串口接收中断 关串口接收中断 开串口发送中断 关串口发送中断 开串口发送完成中断 关串口发送完成中断 枚举详解 枚举定义 1 typedef enum 2{ 3 UART0, 4 UART1, 5 UART2, 6 UART3, 7 UART4, 8 UART5, 9 10 UART_MAX, 11 } UARTn_e; UARTn_e 对应的管脚可在 app/inc/port_cfg.h 文件里配置。 枚举作用 用来定义串口端口号。 山外 函数详解 函数原型 uart_init 1 void uart_init (UARTn_e, uint32 baud); 功能说明 初始化 uartx 模块 UART1 对应的管脚可在 app/inc/port_cfg.h 文件里配置。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 112 ~ 调用例子 攻城略地之一天攻破 K60/KL26 —— 松飞科技 1 uart_init (UART1,19200); //初始化 串口 1 波特率为 19200 山外 函数原型 uart_getchar 1 void uart_getchar (UARTn_e, char *ch); 功能说明 等待接收 1 个字节,如果一直没接收到数据,会一直卡住循环里等到接收到为止。 调用例子 &是取地址 1 char ch; 2 uart_getchar (UART3,&ch); //等待接收 1 个字节,保存到 ch 里 函数原型 uart_querychar 1 char uart_querychar (UARTn_e, char *ch); //查询接收 1 个字符 功能说明 查询接收 1 个字符,若有数据接收,则存储在 ch 形参指定地址上。 函数返回 返回 1 表示接收到数据,返回 0 表示接收不到数据。 调用例子 1 char ch ; &是取地址 2 if( uart_querychar (UART3,&ch) == 1) 3{ 4 printf("成功接收到一个字节"); 5} //查询接收 1 个字符,保存到 ch 里 函数原型 uart_querystr 1 uint32 uart_querystr (UARTn_e, char *str, uint32 max_len); //查询接收字符串 山外论坛 http://vcan123.com 山外メ雲ジ ~ 113 ~ 功能说明 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 查询接收字符串,最后接收到的字符后会自动加入 0x00 表示字符串结尾。 函数返回 接收到的字节数目。 调用例子 1 char str[100]; 2 uint32 num; 3 num = uart_querystr (UART3,&str,100-1); 4 if( num != 0 ) 5{ 6 printf("成功接收到%d 个字节:%s",num,str); 7} 此处为 99 而非 100,因为字符串 str 仅占用 100 个字节,最大只能存储 99 个字节,最后 一个为 0x00,否则就不是字符串了 函数原型 uart_querybuff 1 uint32 uart_querybuff (UARTn_e, char *buff, uint32 max_len); //查询接收 buff 功能说明 查询接收 buff 函数返回 时间到了也接收不了就返回 0,并把 buff 指针指向内容设为 0;成功接收到就返 回 1,并把接收到的数据保存在 buff 指针指向的地址。 调用例子 1 char buff[100]; 2 uint32 num; 3 num = uart_pendbuff (UART3,&buff,100); 4 if( num != 0 ) 5{ 6 printf("成功接收到%d 个字节:%s",num,buff); 7} 山外论坛 http://vcan123.com 山外メ雲ジ ~ 114 ~ 函数原型 攻城略地之一天攻破 K60/KL26 —— 松飞科技 uart_query 1 char uart_query (UARTn_e); 功能说明 查询是否接收到一个字节 函数返回 1 表示接收到一个字节了; 0 表示没有接收到 调用例子 1 char ch; 2 if(uart_query (UART3) == 1) //查询是否接收到数据 3{ 4 ch = uart_getchar (UART3); //等待接收一个数据,保存到 ch 里 5} 函数原型 uart_putchar 1 void uart_putchar (UARTn_e, char ch); 功能说明 发送 1 个字节。 调用例子 1 uart_putchar (UART3, 'A'); //发送字节'A' 函数原型 uart_putbuff 1 void uart_putbuff (UARTn_e , uint8 *buff, uint32 len);//发送 len 个字节 buff 功能说明 发送 len 个字节,包括 NULL 也会发送。 调用例子 1 uart_putbuff (UART3,"1234567", 3); //实际发送了 3 个字节'1','2','3' 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 115 ~ 函数原型 攻城略地之一天攻破 K60/KL26 —— 松飞科技 uart_putstr 1 void uart_putstr (UARTn_e , const uint8 *str); 功能说明 发送字符串。 调用例子 //发送字符串 1 uart_putstr (UART3,"1234567"); //实际发送了 7 个字节 山外 函数原型 uart_rx_irq_en 1 void uart_rx_irq_en (UARTn_e); 功能说明 开串口接收中断。 调用例子 //开串口接收中断 1 uart_rx_irq_en(UART3); //开串口 3 接收中断 函数原型 uart_rx_irq_dis 1 void uart_rx_irq_dis(UARTn_e); 功能说明 关串口接收中断。 调用例子 //关串口接收中断 1 uart_rx_irq_dis(UART3); //关串口 3 接收中断 函数原型 uart_tx_irq_en 1 void uart_tx_irq_en (UARTn_e); 功能说明 开串口发送中断。 //开串口发送中断 山外论坛 http://vcan123.com 山外メ雲ジ ~ 116 ~ 调用例子 攻城略地之一天攻破 K60/KL26 —— 松飞科技 1 uart_tx_irq_en(UART3); //开串口 3 发送中断 函数原型 uart_tx_irq_dis 1 void uart_tx_irq_dis(UARTn_e); 功能说明 关串口发送中断。 调用例子 //关串口发送中断 1 uart_tx_irq_dis(UART3); //关串口 3 发送中断 函数原型 uart_txc_irq_en 1 void uart_txc_irq_en (UARTn_e); 功能说明 开串口发送完成中断。 调用例子 //开串口发送完成中断 1 uart_txc_irq_en(UART3); //开串口 3 发送完成中断 函数原型 uart_txc_irq_dis 1 void uart_txc_irq_dis(UARTn_e); 功能说明 关串口接收中断。 传递参数 参考形参变量的命名及其作用。 //关串口发送完成中断 调用例子 1 uart_txc_irq_dis(UART3); //关串口 3 发送完成中断 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 117 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 UART 综合测试例程 山外 串口接收和发送测试例程 此例程,我们需要串口与电脑通信,那么就需要使用 USB 转 TTL,如果使用山外 K60 无线调试器,那调试器本身配套 USB 转 TTL,直接接入 USB 线即可。 如果使用核心板,那么可以购买 USB 转 TTL 模块,直接接入单片机的串口管脚。 这里,我们调用 uart_init 串口初始化函数,然后学习如何使用 串口发送函数:uart_putstr、uart_putchar; 串口查询接收和接收函数:uart_query、uart_querychar 、uart_getchar 函数。 串口发送 函数 printf 是标准 C 语言函数,可百度搜索其用法。 1 /*! 山外论坛 http://vcan123.com 山外メ雲ジ ~ 118 ~ 2 * @brief main 函数 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 3 * @since v5.0 4 * @note 测试查询接收与发送相关的部分代码 5 */ 6 void main() 7{ 8 char ch; 9 10 //uart_init(UART3,115200); App/Inc/MK60_conf.h 配置文件里: #define VCAN_PORT UART3 #define VCAN_BAUD 115200 (KL26 在 App/Inc/MKL_conf.h,VCAN_PORT 配置为 UART0) 进入 main 函数前会初始化 VCAN_PORT,故此处不用初 始化 //初始化串口(由于 printf 函数 所用的端口就是 UART3,已经初始化了,因此此处不需要再初始化) 11 12 printf("\n 山外论坛:www.vcan123.com"); 13 14 uart_putstr (UART3 , "\n\n\n 接收数据,并进行发送:"); //发送字符串 15 16 while(1) 17 { 18 if(uart_query (UART3) != 0) //查询是否接收到数据 19 { 20 uart_getchar (UART3,&ch); 21 uart_putchar(UART3, ch); //等待接收一个数据,保存到 ch 里 //发送 1 个字节 22 } 23 24 if(uart_querychar (UART3, &ch) != 0) //查询接收 1 个字符 25 { 26 uart_putchar(UART3, ch); //发送 1 个字节 27 } 28 29 //注:上面两个 if 的实现功能都是一样的。 30 31 //uart_getchar 和 uart_querychar 的区别在于 , //前者 需要等待接收到数据,后者查询是否接收到,接收到就接收,接收不到就退出 32 } 33 } 下载程序后,切换到上位机,上位机里一个个发送 1~6 的数字,可以看到这样的 效果: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 119 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 加入我一次性发送 “123456”这 6 个字符串,效果如何呢?当然也是可以正常 接收这 6 个字符串。因为 while 循环里不停查询接收,所以单片机可以及时接收并发 送出去。 假如在 while 循环里加入 DELAY_MS(500); 延时函数,效果会如何?绝大多数的 可能是接收 1 个字符,极小可能是接收到 2 个字符。因为 UART 接收到数据后保存到 山外论坛 http://vcan123.com 山外メ雲ジ ~ 120 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 接收寄存器里,后续由于单片机一直不读取接收寄存器的数据,导致新的数据没法写 进接收寄存器里,从而被丢弃掉。这也是查询接收的缺点,可改用中断接收方式。 串口查询接收多个字符串 本例程,主要学习调用 uart_querystr 函数来接收多个字符串。 1 /*! 2 * @brief main 函数 3 * @since v5.2 4 * @note 测试查询接收多个字符串函数 5 */ 6 void main() 7{ 8 char str[100]; 9 10 //uart_init(UART3,115200); //初始化串口(由于 printf 函数 所用的端口就是 UART3,已经初始化了,因此此处不需要再初始化) 11 12 printf("\n 山外论坛:www.vcan123.com"); 13 14 uart_putstr (UART3 , "\n\n\n 请上位机发送多个字符:"); //发送字符串 15 16 while(1) 查询并接收一个字符串。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 121 ~ 17 { 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 18 if(uart_querystr (UART3,str,sizeof(str)-1) != 0) //查询是否接收到字符串 19 { 20 uart_putstr (UART3, "\n 接收到字符串:"); 21 uart_putstr (UART3, (uint8 *)str); //发送字符串 //发送字符串 22 } 23 } 24 } 这里直接把整个字符串发送出去。 烧录程序后,到上位机里,每次连续发送多个字符,单片机都可正常接收并返回。 串口中断接收例程 由于串口查询接收存在可能接收不到的情况,那可以改用串口中断接收例程。 关键的地方在于调用 uart_rx_irq_en 来开串口接收中断,set_vector_handler 函 数设置中断函数入口为 uart3_handler,那么单片机串口接收到数据就会自动执行 uart3_handler 函数。 1 /*! 2 * @brief UART3 中断服务函数 山外论坛 http://vcan123.com 山外メ雲ジ ~ 122 ~ 3 * @since v5.0 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 4 */ 5 void uart3_handler(void) 6{ 7 char ch; 8 UARTn_e uratn = UART3; 9 10 if(UART_S1_REG(UARTN[uratn]) & UART_S1_RDRF_MASK) //接收数据寄存器满 11 { 12 //用户需要处理接收数据 13 uart_getchar (UART3, &ch); 14 uart_putchar (UART3 , ch); //无限等待接受 1 个字节 //发送字符串 15 } 16 } 17 18 /*! 19 * @brief main 函数 20 * @since v5.0 21 * @note 串口中断接收测试 22 */ 23 void main() 24 { 25 //uart_init(UART3,115200); //初始化串口(UART3 是工程里配置为 printf 函数输出端口,故已经进行初始化) 26 27 uart_putstr (UART3 ,"\n\n\n 接收中断测试:"); //发送字符串 28 29 set_vector_handler(UART3_RX_TX_VECTORn,uart3_handler); // 设置中断服务函数到中断向量表里 30 31 uart_rx_irq_en (UART3); //开串口接收中断 32 33 while(1) 此函数内部已经调 用了 enable_irq 34 { 35 36 } 37 } while 循环为空,可 执行各种处理函数 山外论坛 http://vcan123.com 山外メ雲ジ ~ 123 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 有图有真相,由于是中断里进行接收,因此 while 循环里加入各种延时也不影响 串口的接收。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 124 ~ I2C 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 快速入门:I2C 通信库使用方法 形参变量的命名及其作用 形参变量 I2Cn_e baud SlaveID reg Data 作用 模块号 波特率 7 位从机地址 从机的寄存器地址 数据 I2C0、I2C1 7bit 8bit 8bit 取值 常用枚举列表 I2Cn_e 名称 模块号 功能说明 函数列表 函数名称 i2c_init i2c_write_reg i2c_read_reg 函数功能 初始化 I2C 模块 写入数据到寄存器 读取寄存器的数据 枚举详解 枚举定义 1 /** 2 * @brief I2C 模块编号 3 */ 4 typedef enum 5{ 6 I2C0 = 0, 7 I2C1 = 1 8 } I2Cn_e; I2Cn_e 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 125 ~ 枚举作用 用来定义模块号 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 函数详解 函数原型 i2c_init 1 extern uint32 i2c_init(I2Cn_e i2cn, uint32 baud); 功能说明 初始化 I2C,设置期待的波特率,返回值为实际配置的波特率。 调用例子 1 i2c_init(I2C0,400*1000); // 初始化 I2C0,期待的波特率为 400k //初始化 I2C 函数原型 i2c_write_reg 1 void i2c_write_reg(I2Cn_e, uint8 SlaveID, uint8 reg, uint8 Data); 功能说明 写入一个字节数据到 I2C 设备指定寄存器 //写入数据到寄存器 调用例子 1 i2c_write_reg(I2C0, 0x1D, 1,2); //向从机 0x1D 的寄存器 1 写入数据 2 函数原型 i2c_read_reg 1 uint8 i2c_read_reg (I2Cn_e, uint8 SlaveID, uint8 reg); 功能说明 读取 I2C 设备指定寄存器的一个字节数据 调用例子 1 uint8 value = i2c_read_reg(I2C0, 0x1D, 1); //读取寄存器的数据 山外论坛 http://vcan123.com 山外メ雲ジ ~ 126 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 MMA7455 I2C 通信实验测试 首先定义 MMA7455 所用的 I2C 模块号和从机地址。 1 #define 2 #define MMA7455_DEVICE MMA7455_ADRESS I2C0 (0x1D) //定义 MMA7455 所用的接口 为 I2C0 /*MMA7455_Device Address*/ 利用宏定义包装相关的 I2C 函数,便于修改和移植。 1 //宏定义调用底层的 I2C 接口 2 #define MMA7455_OPEN(baud) 3 #define MMA7455_WR(reg,value) 4 #define MMA7455_RD(reg) i2c_init(MMA7455_DEVICE,baud) i2c_write_reg(MMA7455_DEVICE,MMA7455_ADRESS,reg,value) //mma7455 写寄存器 i2c_read_reg(MMA7455_DEVICE,MMA7455_ADRESS,reg) //mma7455 读寄存器 直接调用封装好的宏定义,容易写出 MMA7455 的初始化代码和读写寄存器的代码: 1 /*! 2 * @brief MMA7455 初始化,进入 2g 量程测试模式 3 * @since v5.0 4 * Sample usage: mma7455_init(); //初始化 MMA7455 5 */ 6 void mma7455_init(void) 7{ 8 MMA7455_OPEN(400 * 1000); //初始化 mma7455 接口,设置波特率 400k 9 10 /*MMA 进入 2g 量程测试模式*/ 11 MMA7455_WR(MMA7455_MCTL, 0x05); 12 13 /*DRDY 标置位,等待测试完毕*/ 14 while(!(MMA7455_RD(MMA7455_STATUS) & 0x01)); 15 } 16 17 18 /*! 19 * @brief MMA7455 写寄存器 山外论坛 http://vcan123.com 山外メ雲ジ ~ 127 ~ 20 * @param reg 21 * @param dat 攻城略地之一天攻破 K60/KL26 —— 松飞科技 寄存器 需要写入的数据的寄存器地址 山外 22 * @since v5.0 23 * Sample usage: mma7455_write_reg(MMA7455_XOFFL,0); // 写寄存器 MMA7455_XOFFL 为 0 24 */ 25 void mma7455_write_reg(uint8 reg, uint8 Data) 26 { 27 MMA7455_WR(reg, Data); 28 } 29 30 /*! 31 * @brief 32 * @param 33 * @param MMA7455 读寄存器 reg 寄存器 dat 需要读取数据的寄存器地址 34 * @since v5.0 35 * Sample usage: uint8 data = mma7455_read_reg(MMA7455_XOFFL); // 读寄存器 MMA7455_XOFFL 36 */ 37 uint8 mma7455_read_reg(uint8 reg) 38 { 39 return MMA7455_RD(reg); 40 } 有了相关的底层接口,那么可以直接调用接口,查询相关的寄存器: 这里直接初始化 MMA7455,然后读取 x、y、z 轴的数据并打印到串口助手。 1 /*! 2 * @brief main 函数 3 * @since v5.0 4 * @note I2C 驱动 MMA7455 5 */ 6 void main(void) 7{ 8 printf("\n\n\n***********三轴加速度测试************"); 9 10 mma7455_init(); 11 12 while(1) 13 { 山外论坛 http://vcan123.com 山外メ雲ジ ~ 128 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 14 //注意:读取的结果需要校准的,否则不准的。 15 //校准方法请看文档,此处仅讲解通信驱动 16 printf("\n\nx:%d,y:%d,z:%d" 17 ,(int8)mma7455_read_reg(MMA7455_XOUT8) //读取 x 轴参数 18 ,(int8)mma7455_read_reg(MMA7455_YOUT8) //读取 y 轴参数 19 ,(int8)mma7455_read_reg(MMA7455_ZOUT8) //读取 z 轴参数 20 ); 21 22 DELAY_MS(500); 23 } 24 } 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 129 ~ SPI 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 快速入门:SPI 通信库使用方法 形参变量的命名及其作用 形参变量 作用 SPIn_e 模块号 SPIn_PCSn_e SPI 模块片选号 SPI_CFG baud modata midata Len mocmd micmd cmdlen 主从机模式配置 期待的波特率 发送数据缓冲区 的地址 接收数据缓冲区 的地址 发送和接收的数 据长度 发送命令缓冲区 的地址 接收命令缓冲区 的地址 发送和接收的命 令长度 SPI0、SPI1、SPI2 可多选: SPIn_PCS0、 SPIn_PCS1、 SPIn_PCS2、 SPIn_PCS3、 SPIn_PCS3、 SPIn_PCS5 MASTER、SLAVE uint32 uint32 取值 常用枚举列表 SPIn_e SPIn_PCSn_e SPI_CFG 名称 功能说明 模块号 SPI 模块片选号 主从机模式配置 函数列表 函数名称 山外论坛 http://vcan123.com 函数功能 山外メ雲ジ 山外 ~ 130 ~ spi_init spi_mosi spi_mosi_cmd 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 SPI 初始化,选择片选信号,设置模式, 波特率 SPI 发送接收函数,发送 databuff 数据,并 把接收到的数据存放在 databuff 里(注 意,会覆盖原来的 databuff) SPI 发送接收函数,与 spi_mosi 相比,多 了先发送 cmd 缓冲区的步骤,即命令数 据分开两部分发送 枚举详解 枚举定义 1 /** 2 * @brief SPI 模块号 3 */ 4 typedef enum 5{ 6 SPI0, 7 SPI1, 8 SPI2 9 } SPIn_e; SPIn_e 枚举作用 用来定义模块号 枚举定义 SPIn_PCSn_e 1 /** 2 * @brief SPI 模块片选号 3 */ 4 typedef enum 5{ 6 SPIn_PCS0 = 1 << 0, 7 SPIn_PCS1 = 1 << 1, 8 SPIn_PCS2 = 1 << 2, 9 SPIn_PCS3 = 1 << 3, 山外论坛 http://vcan123.com 山外メ雲ジ ~ 131 ~ 10 SPIn_PCS4 = 1 << 4, 11 SPIn_PCS5 = 1 << 5, 12 } SPIn_PCSn_e; 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 枚举作用 SPI 模块片选号,需要移位操作是为了保证每个片选信号占用不同的位,从而可 以多选。 枚举定义 1 /** 2 * @brief 主从机模式 3 */ 4 typedef enum 5{ 6 MASTER, //主机模式 7 SLAVE //从机模式 8 } SPI_CFG; 枚举作用 主从机模式 SPI_CFG 函数详解 函数原型 spi_init 目前代码仅支 持主机模式 1 uint32 spi_init (SPIn_e, SPIn_PCSn_e , SPI_CFG,uint32 baud); //SPI 初始化,选择片选信号,设置模式,波特率 功能说明 SPI 初始化,选择片选信号,设置模式,波特率。返回值为实际配置的波特率。 调用例子 1 uint32 baud = spi_init(SPI0,SPIn_PCS0, MASTER,10*1000*1000); //初始化 SPI,选择 CS0,主机模式, 波特率为 1M ,返回真实波特率到 baud 变量 山外论坛 http://vcan123.com 山外メ雲ジ ~ 132 ~ 函数原型 攻城略地之一天攻破 K60/KL26 —— 松飞科技 spi_mosi SPI 输出的同时也会进行输 入,若不需要输入或输出, 只需要把地址设为 NULL 山外 1 void spi_mosi (SPIn_e,SPIn_PCSn_e,uint8 *modata,uint8 *midata,uint32 len); //SPI 发送接收函数,发送 databuff 数据,并把接收到的数据存放在 databuff 里(注意,会覆盖原来的 databuff) 功能说明 SPI 发送接收函数,发送 databuff 数据,并把接收到的数据存放在 databuff 里(注 意,会覆盖原来的 databuff)。 调用例子 1 void spi_mosi(SPI0,SPIn_PCS0,buff,buff,2); //发送 buff 的内容,并接收到 buff 里,长度为 2 字节 函数原型 spi_mosi_cmd 与 spi_mosi 相比, 多了这些参数 1 void spi_mosi_cmd(SPIn_e,SPIn_PCSn_e,uint8 *mocmd, uint8 *micmd , uint8 *modata, uint8 *midata, uint32 cmdlen , uint32 len); //SPI 发送接收函数,与 spi_mosi 相比,多了先发送 cmd 缓冲区的步骤,即分开两部分发送 功能说明 SPI 发送接收函数,与 spi_mosi 相比,多了先发送 cmd 缓冲区的步骤,即分开两 部分发送 调用例子 这里不接收命令输入 数据,设为 NULL 1 spi_mosi_cmd (SPI0,SPIn_PCS0,cmd,NULL,buff,buff,1,2); //发送 cmd/buff 的内容,不接收 cmd 发送时的数据,接收 buff 发送时的数据到 buff 里,长度分别为 1、2 字节 SPI 通信实验测试 我们配套的 SPI 例程用于驱动 NRF24L01+,这里仅仅提供 NRF24L01+的几个底 层调用 SPI API 接口的例子: 1 /*! 2 * @brief 3 * @param 4 * @param 5 * @return NRF24L01+写寄存器 reg 寄存器 dat 需要写入的数据 NRF24L01+ 状态 山外论坛 http://vcan123.com 山外メ雲ジ ~ 133 ~ 6 * @since v5.0 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 7 * Sample usage: nrf_writereg(NRF_WRITE_REG + RF_CH, CHANAL); //设置 RF 通道为 CHANAL 8 */ 9 uint8 nrf_writereg(uint8 reg, uint8 dat) 10 { 11 12 13 14 uint8 buff[2]; buff[0] = reg; buff[1] = dat; //先发送寄存器 //再发送数据 把数据和命令放入同一个数组里, 然后调用 spi_mosi 发送出去,第 一个接收到的数据为 NRF 的状态 寄存器的值,因此此处直接把数据 接收到原先的数组 buff 里。 15 16 spi_mosi(NRF_SPI, NRF_CS, buff, buff, 2); //发送 buff 里数据,并采集到 buff 里 17 18 /*返回状态寄存器的值*/ 19 return buff[0]; 20 } 1 /*! 2 * @brief 3 * @param 4 * @param 5 * @param 6 * @return NRF24L01+写寄存器一串数据 reg 寄存器 pBuf 需要写入的数据缓冲区 len 需要写入数据长度 NRF24L01+ 状态 这里命令和数据分开发送,直接调 用 spi_mosi_cmd 函数即可。 7 * @since v5.0 8 * Sample usage: 点地址 nrf_writebuf(NRF_WRITE_REG+TX_ADDR,TX_ADDRESS,TX_ADR_WIDTH); //写 TX 节 9 */ 10 uint8 nrf_writebuf(uint8 reg , uint8 *pBuf, uint32 len) 11 { 12 spi_mosi_cmd(NRF_SPI, NRF_CS, ® , NULL, pBuf, NULL, 1 , len); //发送 reg ,pBuf 内容,不接收 13 return reg; //返回 NRF24L01 的状态 14 } 山外论坛 http://vcan123.com 山外メ雲ジ ~ 134 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 VCAN_NRF24L01+ 模块 快速入门:NRF24L01+库使用方法 NRF24L01+的硬件配置: K60 1 //以下是 K60 库硬件配置 2 #define NRF_SPI 3 #define NRF_CS 4 5 #define NRF_CE_PTXn 6 #define NRF_IRQ_PTXn SPI0 SPIn_PCS0 PTE28 PTE27 SPI 模块和 片选配置 这两个可选普通 IO,其中 IRQ 管脚需要支持中断 山外 1 //以下是 KL26 库硬件配置 2 #define NRF_SPI 3 #define NRF_CS 4 5 #define NRF_CE_PTXn 6 #define NRF_IRQ_PTXn SPI1 SPI_PCS0 PTE5 PTC18 KL26 KL26 仅 A/C/D 端口支持中断 NRF24L01+的特性配置: 如果不熟悉这里的配置,不建议修 改。最常用修改的是接收 FIFO 的包 数目。 1 #define DATA_PACKET 32 //一次传输最大可支持的字节数(1~32) 2 #define RX_FIFO_PACKET_NUM 80 //接收 FIFO 的 包 数目 ( 总空间 必须要大于 一副图像的大小,否则 没法接收完 ) 3 #define ADR_WIDTH 5 //定义地址长度(3~5) 4 #define IS_CRC16 1 //1 表示使用 CRC16,0 表示 使用 CRC8 (0~1) NRF24L01+无线传输的匹配信息,必须收发双方设置相同的数据才可正常收发。 建议自行修改成不同的配置,避免与别人 冲突(VCAN_NRF24L0.c 里定义)。 1 uint8 TX_ADDRESS[5] = {0x34, 0x43, 0x10, 0x10, 0x01}; 2 uint8 RX_ADDRESS[5] = {0x34, 0x43, 0x10, 0x10, 0x01}; 3 4 #define CHANAL 40 // 定义一个静态发送地址 推荐修改 //频道选择 山外论坛 http://vcan123.com 山外メ雲ジ ~ 135 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 常用枚举列表 名称 nrf_tx_state_e 功能说明 NRF 模块发送状态 山外 函数列表 函数名称 nrf_init nrf_link_check nrf_rx nrf_tx nrf_tx_state nrf_handler 函数功能 初始化 NRF24L01+ 检测 NRF24L01+与单片机是否通信正常 接收数据 发送数据 检查发送状态(发送数据后查询是否发送 成功) NRF24L01+ 的 中断服务函数 枚举详解 枚举定义 nrf_tx_state_e 1 /** 2 * @brief NRF 模块发送状态 3 */ 4 typedef enum 5{ 6 NRF_TXING, 7 NRF_TX_ERROR, 8 NRF_TX_OK, 9 } nrf_tx_state_e; //发送中 //发送错误 //发送完成 发送数据后查询是 否发送成功 枚举作用 用来定义 NRF 模块发送状态 山外论坛 http://vcan123.com 山外メ雲ジ ~ 136 ~ 函数详解 攻城略地之一天攻破 K60/KL26 —— 松飞科技 函数原型 nrf_init 1 uint8 nrf_init(void); //初始化 NRF24L01+ 功能说明 初始化 NRF24L01+。返回 0 为初始化失败,1 为初始化成功。 山外 调用例子 1 while(!nrf_init()) 2{ 3 printf("\n NRF 与 MCU 连接失败,请重新检查接线。\n"); 4} 5 6 printf("\n NRF 与 MCU 连接成功!\n"); //初始化 NRF24L01+ ,等待初始化成功为止 函数原型 nrf_link_check 1 uint8 nrf_link_check(void); //检测 NRF24L01+与单片机是否通信正常 功能说明 检测 NRF24L01+与单片机是否通信正常。返回 0 表示通信不正常,1 表示正常 调用例子 1 while(nrf_link_check() == 0) 2{ 3 printf("\n 通信失败"); 4} 函数原型 nrf_rx 1 uint32 nrf_rx(uint8 *rxbuf, uint32 len); //接收数据 功能说明 NRF24L01+数据接收,len 为最大接收的长度,返回值为实际接收的长度。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 137 ~ 调用例子 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 1 relen = nrf_rx(buff,DATA_PACKET); 2 if(relen != 0) 3{ 4 printf("\n 接收到数据:%s",buff); 5} //等待接收一个数据包,数据存储在 buff 里 //打印接收到的数据 如果接收到的数据是 字符串,才可以用 printf 打印出来哦! 函数原型 nrf_tx 1 uint8 nrf_tx(uint8 *txbuf, uint32 len); //发送数据 功能说明 NRF24L01+数据发送。返回值 0 表示发送失败,1 为发送中。最终发送结果需要 调用 nrf_tx_state()判断。 调用例子 1 if(nrf_tx(buff,DATA_PACKET) == 1 ); //发送一个数据包:buff(包为 32 字节) 2{ 3 //等待发送过程中,此处可以加入处理任务 4 假如之前发送的数据还没发送完成,那么再次发送的时候 就会发送失败,由于此例程有用 nrf_tx_state 函数来等待 发送 完成,因此不会出现 返回 0 的情况出现。 5 while(nrf_tx_state() == NRF_TXING); //等待发送完成 6 7 if( NRF_TX_OK == nrf_tx_state () ) 8 { 发送后需要调用 nrf_tx_state 查询发送结果 9 printf("\n 发送成功:%d",i); 10 i++; //发送成功则加 1,可验证是否漏包 11 } 12 else 13 { 发送过程中,buff 的数据不要修 改,否则可能发送的数据有误。 14 printf("\n 发送失败:%d",i); 15 } 16 } 17 else 18 { 19 printf("\n 发送失败:%d",i); 20 } 山外论坛 http://vcan123.com 山外メ雲ジ ~ 138 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 函数原型 nrf_tx_state 1 nrf_tx_state_e nrf_tx_state (); //检查发送状态(发送数据后查询是否发送成功) 功能说明 检查发送状态(发送数据后查询是否发送成功),返回值类型在 nrf_tx_state_e 枚 举里定义。 调用例子 参考 nrf_tx 函数的例子 函数原型 nrf_handler 1 void nrf_handler(void); //NRF24L01+ 的 中断服务函数 功能说明 NRF24L01+ 的 中断服务函数,需要把此函数放入 IRQ 管脚所对应的中断函数里。 调用例子 1 /*! 2 * @brief 3 * @since PORTE 中断服务函数 v5.0 4 */ 5 void PORTE_IRQHandler() 6{ 7 uint8 n; //引脚号 8 uint32 flag; 9 10 flag = PORTE_ISFR; 11 PORTE_ISFR = ~0; 12 13 n = 27; 14 if(flag & (1 << n)) 15 { 16 nrf_handler(); 17 } 18 } //清中断标志位 //PTE27 触发中断 直接中断里调用此函数即可。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 139 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 NRF24L01+无线通信实验测试 因为要无线收发,因此这里需要使用两个核心板和两个 NRF24L01+模块。 发送方代码: 1 void PORTE_IRQHandler(); 2 3 /*! 4 * @brief main 函数 5 * @since v5.0 6 * @note SPI 驱动 NRF24L01+ 7 */ 8 void main(void) 9{ 10 uint32 i=0; 11 uint8 buff[DATA_PACKET]; 12 uint8 *str = "欢迎使用山外 K60 开发板!"; 13 14 printf("\n\n\n***********无线模块 NRF24L01+测试************"); 15 初始化 16 while(!nrf_init()) //初始化 NRF24L01+ ,等待初始化成功为止 17 { 山外论坛 http://vcan123.com 山外メ雲ジ ~ 140 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 18 printf("\n NRF 与 MCU 连接失败,请重新检查接线。\n"); 山外 19 } 20 set_vector_handler(PORTE_VECTORn ,PORTE_IRQHandler); 配置中断入口 //设置 PORTE 的中断服务函数为 PORTE_VECTORn 21 enable_irq(PORTE_IRQn); 22 23 printf("\n NRF 与 MCU 连接成功!\n"); 24 25 while(1) 26 { sprintf 的用法与 printf 类似,只 不过输出的数据存储在 buff 数组 27 sprintf((char *)buff,"%s%d",str,i); //把 str 和 i 合并成一个字符串到 buff 里,再进行发送 28 if(nrf_tx(buff,DATA_PACKET) == 1 ); //发送一个数据包:buff(包为 32 字节) 29 { 30 //等待发送过程中,此处可以加入处理任务 把 buff 数据无线发送出去 31 32 while(nrf_tx_state() == NRF_TXING); //等待发送完成 33 34 if( NRF_TX_OK == nrf_tx_state () ) 35 { 等待发送完成。 发送过程中可以处理其他任务。 36 printf("\n 发送成功:%d",i); 37 i++; //发送成功则加 1,可验证是否漏包 38 } 39 else 40 { 判断是否发送成功,发送成功就进 行 i++,目的是对数据进行编号。 41 printf("\n 发送失败:%d",i); 42 } 43 } 44 else 45 { 46 printf("\n 发送失败:%d",i); 47 } 48 DELAY_MS(10); 49 } 50 } 51 52 /*! 53 * @brief PORTE 中断服务函数 54 * @since v5.0 55 */ 山外论坛 http://vcan123.com 山外メ雲ジ ~ 141 ~ 56 void PORTE_IRQHandler() 57 { 58 59 uint8 n; //引脚号 uint32 flag; 60 61 flag = PORTE_ISFR; 62 PORTE_ISFR = ~0; 63 64 n = 27; 65 if(flag & (1 << n)) 66 { 67 nrf_handler(); 68 } 69 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 //清中断标志位 //PTE27 触发中断 中断里直接执行 NRF 的中断处理函 数。 山外 接收方代码: 1 void PORTE_IRQHandler(); 2 3 /*! 4 * @brief main 函数 5 * @since v5.0 6 * @note SPI 驱动 NRF24L01+ 7 */ 8 void main(void) 9{ 10 uint8 buff[DATA_PACKET]; //定义接收缓冲区 11 uint8 relen; 12 13 printf("\n\n\n***********无线模块 NRF24L01+测试************"); 14 15 while(!nrf_init()) //初始化 NRF24L01+ ,等待初始化成功为止 16 { 17 printf("\n NRF 与 MCU 连接失败,请重新检查接线。\n"); 18 } 19 //配置中断服务函数 20 set_vector_handler(PORTE_VECTORn ,PORTE_IRQHandler); 初始化、配置中断都与发送 方相同。 //设置 PORTE 的中断服务函数为 PORTE_VECTORn 21 enable_irq(PORTE_IRQn); 山外论坛 http://vcan123.com 山外メ雲ジ ~ 142 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 22 23 printf("\n NRF 与 MCU 连接成功!\n"); 24 25 while(1) 26 { 如果没发送过来,就会查不 到有数据接收。 27 relen = nrf_rx(buff,DATA_PACKET); //等待接收一个数据包,数据存储在 buff 里 28 if(relen != 0) 29 { 30 printf("\n 接收到数据:%s",buff); //打印接收到的数据 31 } 32 } 33 } 34 因为发送方发送的是字符串,所 以这里直接 printf 打印出来。 35 /*! 36 * @brief PORTE 中断服务函数 37 * @since v5.0 38 */ 39 void PORTE_IRQHandler() 40 { 41 uint8 n; //引脚号 42 uint32 flag; 中断函数,与发送方相同。 43 44 flag = PORTE_ISFR; 45 PORTE_ISFR = ~0; //清中断标志位 46 47 n = 27; 48 if(flag & (1 << n)) //PTE27 触发中断 49 { 50 nrf_handler(); 51 } 52 } 先烧录接收方的例程,因为只有在有接收方应答的情况下,发送方才可正常发送 成功(无应答则发送失败)。 核心板烧录接收方的例程,开发板烧录发送方的例程,那么通过开发板上的 USB 转串口,可以看到发送方显示的数据: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 143 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 假如关掉接收方,那么由于没有接收方做应答,就会出现发送失败的情况: 两块板子交换程序,即开发板上为接收方代码,那么串口调试助手上看到每次接 收到的数据: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 144 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 145 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 K60_FTM/KL26_TPM 模块 山外 K60 的 FTM 模块是在 TPM 模块的基础上改进而来,增加了正交解码等功能,因 此两者都是基本相同的。 为了方便对比,我们把 K60 的 FTM 和 KL26 的 TPM 都写在同一章节。 快速入门:PWM 库使用方法 形参变量的命名及其作用 KL26 也是相应的参 数,只需要把 FTM 改 成 TPM。 形参变量 作用 取值 FTMn_e 模块名 FTM0, FTM1, FTM2,FTM3 (FX 可选 FTM3) FTM_CH0, FTM_CHn_e 通道号 FTM_CH1, FTM_CH2, FTM_CH3, FTM_CH4, FTM_CH5, FTM_CH6, 管脚在 app/inc/port_cfg.h 文 件里配置。 Freq 频率 FTM_CH7, Duty 通道占空比 通道占空比为:duty /FTM_PRECISON FTM_PRECISON 为精度宏定义,定义为 100 FTM_Input_cfg FTM 输入捕捉配 置 FTM_Rising, FTM_Falling, FTM_Rising_or_Falling //上升沿捕捉 //下降沿捕捉 //跳变沿捕捉 分频因子 FTM_PS_1, FTM_PS_2, FTM_PS_e FTM_PS_4, 分频值 = (1<< FTM_PS_8, FTM_PS_e) , 例 FTM_PS_16, 如 FTM_PS_2 FTM_PS_32, 对应的 分频值 = FTM_PS_64, (1<=0) { printf("\n 正转:%d",val); } else { printf("\n 反转:%d",-val); } PIT_Flag_Clear(PIT0); //清中断标志位 山外 编译下载后,可以用手来转动编码器,通过串口调试助手来看到实验结果。如下 图,手动转动编码器,那么编码器就会产生脉冲,从而通过串口助手来显示脉冲计数 值,如果反向转动编码器,那么串口助手里就会提示反转方向。 KL26 TPM 脉冲计数测试例程 KL26 的 TPM 模块有两个外部时钟管脚可供使用:TPM_CLKIN0 和 TPM_CLKIN1。 每个 TPM 模块都可选择这两个外部时钟管脚中的一个来作为脉冲计数管脚。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 170 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 为了测试方便,这里直接用 TPM0_CH0 来产生 PWM 波,TPM2 作为脉冲计数的 计数器,选用 TPM_CLKIN0 为脉冲输入管脚。 在 port_cfg.h 文件里查询到 TPM0_CH0_PIN 为 PTE24,TPM_CLKIN0_PIN 为 PTC12,即需要短接这两个管脚。 具体的测试代码如下: 1 /*! 2 * @brief main 函数 3 * @since v5.0 4 * @note 此处利用 TPM0 模块产生 PWM,TPM2 进行累加 PWM 脉冲 5 */ 6 void main() 7{ 8 uint16 count; 9 10 printf("\n*****TPM 脉冲计数 测试*****\n"); 11 12 tpm_pwm_init(TPM0, TPM_CH0,1000,50); //初始化 PWM,管脚是 TPM0_CH0_PIN 13 tpm_pulse_init(TPM2,TPM_CLKIN0,TPM_PS_1); //初始化 TPM2 为脉冲累加,输入管脚为 TPM_CLKIN0_PIN , 分频系数为 1 14 15 // TPM0~TPM2 可任意选择 TPM_CLKIN0 /TPM_CLKIN1 来作为计数口,例如此处是 TPM2 选择 TPM_CLKIN0,也可 以 TPM1 选择 TPM_CLKIN0 等 16 17 while(1) 18 { 山外论坛 http://vcan123.com 山外メ雲ジ ~ 171 ~ 19 20 21 22 23 24 25 26 } 27 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 tpm_pulse_clean(TPM2); //清空脉冲计数器计算值(开始新的计数) DELAY_MS(1000); //利用 延时时间,TPM0 模块产生 PWM,TPM2 进行累加 PWM 脉冲 count = tpm_pulse_get(TPM2); //保存脉冲计数器计算值 printf("脉冲计数为:%d\n", count); //打印计数值 编译下载后,可看到上位机显示的脉冲值为 1000 左右。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 172 ~ PIT 定时中断模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K60 有 4 个 PIT 模块,各有不同的中断 号; KL26 有 2 个 PIT 模块,共用相同的中断号。 因此,K60 的每个 PIT 都有独立的中断函数,而 KL26 的 PIT 都共用一个中断函 数,中断里需要判断哪个 PIT 触发的中断。 两者都是用 bus 时钟来作为时钟源。 讲 PIT 定时器之前,先普及一下基础知识: 1 ms(毫秒)=0.001 秒 1 us(微秒)=0.000001 秒 1 ns(纳秒)=0.000000001 秒 快速入门:PIT 定时中断库使用方法 形参变量的命名及其作用 形参变量 PITn cnt ms us ns 作用 模块号 取值 PIT0, PIT1, PIT2, PIT3 KL26 只有 PIT0 和 PIT1 定时器计数 32 位 时间,单位为毫秒 ms ms * bus_clk_khz 不大于 32 位 时间,单位为微秒 us us * bus_clk_khz/1000 不大于 32 位 时间,单位为纳秒 ns ns * bus_clk_khz/1000000 不大于 32 位 常用枚举列表 PITn_e 名称 函数列表 函数名称 pit_init pit_delay pit_time_start 模块号 功能说明 因为 K60 和 KL26 的函数 都是相 同的,因此合并在一起。 函数功能 初始化 pit 定时器 PIT 延时(不需要初始化的) PIT 开始计时 山外论坛 http://vcan123.com 山外メ雲ジ ~ 173 ~ pit_time_get pit_close 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 获取 PITn 计时时间(超时时会关闭 定时 器)(单位为 bus 时钟)(若值为 0xFFFFFFFF,则表示溢出) 关闭 PIT 宏定义列表 宏定义名词 pit_init_ms pit_init_us pit_init_ns pit_delay_ms pit_delay_us pit_delay_ns pit_time_get_ms pit_time_get_us PIT_Flag_Clear 功能说明 初始化 pit 定时器,ms 级定时中断 初始化 pit 定时器,us 级定时中断 初始化 pit 定时器,ns 级定时中断 PIT 延时(不需要初始化的) PIT 延时(不需要初始化的) PIT 延时(不需要初始化的) 获取 PITn 计时时间 获取 PITn 计时时间 清中断标志 枚举详解 枚举定义 1 //定义 PIT 模块号 2 typedef enum 3{ 4 PIT0, 5 PIT1, 6 PIT2, 7 PIT3, 8 9 PIT_MAX, 10 } PITn_e; PITn_e K60 有 4 个 PIT,KL26 只有 2 个 PIT。 KL26 没有 PIT2 和 PIT3。 枚举作用 用来定义模块号 山外论坛 http://vcan123.com 山外メ雲ジ ~ 174 ~ 函数详解 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 函数原型 pit_init 1 void pit_init(PITn_e, uint32 cnt); //初始化 PITn,并设置定时时间(单位为 bus 时钟周期) 功能说明 初始化 PITn,并设置定时时间(单位为 bus 时钟周期)。 由于 bus 时钟频率 bus_clk_khz 是可知的,因此我们可以用宏定义来封装其他 ms、 us、ns 时间的函数: 1 #define pit_init_ms(PITn_e,ms) 2 #define pit_init_us(PITn_e,us) 3 #define pit_init_ns(PITn_e,ns) pit_init(PITn_e,ms * bus_clk_khz); //初始化 PITn,并设置定时时间(单位为 ms) pit_init(PITn_e,us * bus_clk_khz/1000); //初始化 PITn,并设置定时时间(单位为 us) pit_init(PITn_e,ns * bus_clk_khz/1000000); //初始化 PITn,并设置定时时间(单位为 ns) 调用例子 1 pit_init(PIT0, 1000); 2 set_vector_handler(PIT0_VECTORn,pit_hander); 3 enable_irq(PIT0_IRQn); //定时 1000 个 bus 时钟 后中断 // 设置中断服务函数到中断向量表里 // 使能 PIT 中断 KL26 的 PIT 是共用中断号,此处需要把 PIT0_VECTORn 和 PIT0_IRQn 改成 :PIT_VECTORn 和 PIT_IRQn,然后 在中断函数里判断是哪个 PIT 进入中断。 函数原型 pit_delay 1 void pit_delay(PITn_e, uint32 cnt); 功能说明 PIT 延时(不需要初始化的),延时时间单位为 bus 时钟周期。 由于 bus 时钟频率 bus_clk_khz 是可知的,因此我们可以用宏定义来封装其他 ms、 us、ns 时间的函数: 1 #define 2 #define pit_delay_ms(PITn_e,ms) pit_delay(PITn_e,ms * bus_clk_khz); pit_delay_us(PITn_e,us) pit_delay(PITn_e,us * bus_clk_khz/1000); 山外论坛 http://vcan123.com 山外メ雲ジ //PIT 延时 ms //PIT 延时 us ~ 175 ~ 3 #define 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 pit_delay_ns(PITn_e,ns) pit_delay(PITn_e,ns * bus_clk_khz/1000000);//PIT 延时 ns 调用例子 1 pit_delay(PIT0, 1000); //延时 1000 个 bus 时钟 函数原型 pit_time_start 1 void pit_time_start (PITn_e); 功能说明 PIT 开始计时(不需要初始化 ,直接调用) 调用例子 1 pit_time_start(PIT0); //PIT0 计时开始 //PIT 开始计时 函数原型 pit_time_get 1 uint32 pit_time_get (PITn_e); //获取 PITn 计时时间(超时时会关闭 定时器)(单位为 bus 时钟)(若值为 0xFFFFFFFF,则表示溢出) 功能说明 获取 PITn 计时时间(超时时会关闭 定时器)(单位为 bus 时钟)(若值为 0xFFFFFFFF,则表示超时溢出) 调用例子 1 uint32 time = pit_time_get(PIT0); 2 if(time != ~0) //没超时 3{ 4 printf("\n 计时时间为:%d us",time*1000/bus_clk_khz); 5} //获取 PITn 计时时间 函数原型 1 void pit_close (PITn_e pitn); pit_close //关闭 PIT 山外论坛 http://vcan123.com 山外メ雲ジ ~ 176 ~ 功能说明 关闭 PIT 调用例子 1 pit_close(PIT0); 攻城略地之一天攻破 K60/KL26 —— 松飞科技 //关闭 PIT0 山外 宏定义 PIT_Flag_Clear 定义 1 #define PIT_Flag_Clear(PITn_e) PIT_TFLG(PITn_e)|=PIT_TFLG_TIF_MASK //清中断标志(写 1 清空标志位) 功能说明 清 PITn 中断标志。一般在中断服务函数和 pit 初始化函数里用到。 调用例子 1 PIT_Flag_Clear(PIT0); PIT 测试例程 PIT_DELAY 延时测试例程 本例程,我们用 LED 闪烁,中间插入 PIT 延时函数,通过 LED 灯的闪烁速度从 而校验 PIT 定时器是否准确。 烧录本例程后,可以看到 LED 灯闪烁频率为 2Hz。 1 /*! 2 * @brief main 函数 3 * @since 4 * @note v5.0 山外 PIT 延时实验 5 */ 6 void main() 7{ 8 led_init(LED0); 9 while(1) 10 { 11 led_turn(LED0); //初始化 LED //反转 LED0 状态,使得 LED0 闪烁 山外论坛 http://vcan123.com 山外メ雲ジ ~ 177 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 12 pit_delay_ms(PIT0, 1000); //使用 PIT0 延时: 1000ms 13 } 14 } PIT_TIMING 定时中断测试例程 PIT 定时中断,设定 1 秒进行一次中断,中断里闪烁 LED 灯。即 LED 灯闪烁频率 为 2Hz。 PIT 定时例程,需要使用到中断功能。需要强调一点,K60 每个 PIT 都有独立的 中断号,而 KL26 的 PIT 共用相同的中断号,因此 K60 的每个 PIT 都有独立的中断函 数,而 KL26 的 PIT 共用相同的中断函数,在中断函数内根据标志位来判断是哪个 PIT 进入中断。 K60 的 PIT 定时中断测试例程: 1 /*! 2 * @brief PIT0 中断服务函数 3 * @since v5.0 4 */ 5 void PIT0_IRQHandler(void) 6{ 7 led_turn(LED0); 无需判断标志位 //闪烁 LED0 8 9 PIT_Flag_Clear(PIT0); //清中断标志位 10 } 11 12 /*! 13 * @brief main 函数 14 * @since 15 * @note v5.0 山外 PIT 定时中断实验 16 */ 17 void main() 18 { 19 led_init(LED0); 20 21 pit_init_ms(PIT0, 1000); 22 set_vector_handler(PIT0_VECTORn ,PIT0_IRQHandler); //初始化 LED0,PIT0 中断用到 LED0 //初始化 PIT0,定时时间为: 1000ms 山外论坛 http://vcan123.com 山外メ雲ジ ~ 178 ~ 23 24 25 26 27 28 29 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 //设置 PIT0 的中断服务函数为 PIT0_IRQHandler enable_irq (PIT0_IRQn); //使能 PIT0 中断 while(1) { //这里不需要执行任务,等待中断来闪烁 LED0 } K60 每个 PIT 都有独立的中断号,因此这里是 PIT0_VECTORn 和 PIT0_IRQn KL26 的 PIT 定时中断测试例程: 1 /*! 2 * @brief PIT0 中断服务函数 3 * @since v5.0 4 */ 5 void PIT_IRQHandler(void) 6{ 7 //PIT0 和 PIT1 共用相同中断号,所以两者都共用相同中断函数,需要根据标志位来判断是由哪个 PIT 触发中断 8 9 if(PIT_TFLG(PIT0) == 1 ) //判断是否 PIT0 进入中断 10 { 11 led_turn(LED0); //闪烁 LED0 需要判断标志位 12 13 PIT_Flag_Clear(PIT0); //清中断标志位 14 } 15 } 16 17 /*! 18 * @brief main 函数 19 * @since v5.0 20 * @note 山外 PIT 定时中断实验 21 */ 22 void main() 23 { 24 led_init(LED0); //初始化 LED0,PIT0 中断用到 LED0 25 26 pit_init_ms(PIT0, 1000); //初始化 PIT0,定时时间为: 1000ms 27 set_vector_handler(PIT_VECTORn ,PIT_IRQHandler); //设置 PIT0 的中断服务函数为 PIT_IRQHandler 山外论坛 http://vcan123.com 山外メ雲ジ ~ 179 ~ 28 29 30 31 32 33 34 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 enable_irq (PIT_IRQn); //使能 PIT0 中断 山外 while(1) { //这里不需要执行任务,等待中断来闪烁 LED0 } KL26 的 PIT 共用相同的中断号,因此这里是 PIT_VECTORn 和 PIT_IRQn PIT_TIME 计时测试例程 本例程,我们直接利用其他定时器来作为延时,然后 PIT 测延时时间是多少,从 而可以校验 PIT 计时时间是否准确。 1 /*! 2 * @brief main 函数 3 * @since v5.0 4 * @note 山外 PIT 计时实验 5 */ 6 void main() 7{ 8 uint32 timevar; 9 while(1) 10 { 11 pit_time_start (PIT0); //开始计时 12 DELAY_MS(50); //延时一段时间(由于语句执行需要时间,因而实际的延时时间会更长一些) 13 timevar = pit_time_get_us (PIT0); //获取计时时间 14 15 //pit_close (PIT0); //关闭 PIT(可选择是否关闭) 16 17 printf("\n\n 计时时间为:%dus",timevar); //打印延时时间 18 19 DELAY_MS(1000); 20 } 21 } 利用串口助手, 山外论坛 http://vcan123.com 山外メ雲ジ ~ 180 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 说明一下,DELAY_MS 是一个宏定义,在配置文件 MK60_conf.h 或 MKL_conf.h 里定义,可选配各种不同模块进行延时。(下面的代码选择了 SysTick 滴答定时器作 为延时定时器)。 1 /* 2 * 配置延时函数 3 */ 4 #if 0 条件编译。 0 表示条件不成立,不编译这部分的内容。 5 #include "MKL_lptmr.h" 6 #define DELAY() lptmr_delay_ms(500) 7 #define DELAY_MS(ms) lptmr_delay_ms(ms) 8 #define DELAY_US(us) lptmr_delay_us(us) 9 #elif 0 10 #include "MKL_pit.h" 条件编译。 0 表示条件不成立,不编译这部分的内容。 11 #define DELAY() pit_delay_ms(PIT1,500) 12 #define DELAY_MS(ms) pit_delay_ms(PIT1,ms) 13 #define DELAY_US(us) pit_delay_us(PIT1,us) 14 #else 15 #include "MKL_SysTick.h" 条件编译。 最后编译下面这部分内容,即 SysTick 滴答定时器。 16 #define DELAY() systick_delay_ms(500) 17 #define DELAY_MS(ms) systick_delay_ms(ms) 18 #define DELAY_US(us) systick_delay_us(us) 山外论坛 http://vcan123.com 山外メ雲ジ ~ 181 ~ 19 #endif 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 182 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 LPTMR 低功耗定时器模块 山外 K60 和 KL26 都各有 一个 LPTMR 模块,两者的用法都是相同的。 LPTMR 可用于延时、定时、计时、脉冲计数等功能,前 3 个功能与 PIT 类似。 快速入门:LPTMR 库使用方法 形参变量的命名及其作用 KL26 只有 PIT0 和 PIT1 形参变量 作用 LPT0_ALTn LPTMR 脉冲计数输 入管脚选项 LPT_CFG LPTMR 脉冲计数触 发方式 ms 时间,单位为毫秒 ms us 时间,单位为微秒 us 取值 LPT0_ALT1(PTA19), LPT0_ALT2(PTC5) LPT_Rising, LPT_Falling 16 位 16 位 常用枚举列表 LPT0_ALTn LPT_CFG 名称 功能说明 LPTMR 脉冲计数输入管脚选项 LPTMR 脉冲计数触发方式 函数列表 函数名称 lptmr_delay_ms lptmr_delay_us lptmr_timing_ms lptmr_timing_us lptmr_time_start_ms lptmr_time_get_ms lptmr_time_start_us lptmr_time_get_us lptmr_time_close lptmr_pulse_init lptmr_pulse_get 因为 K60 和 KL26 的函数 都是相 同的,因此合并在一起。 函数功能 延时(ms) 延时(us) 定时(ms) 定时(us) 开始计时(ms) 获取计时时间 开始计时(ns) 获取计时时间 关闭计时器 计数器初始化设置 获取计数值 山外论坛 http://vcan123.com 山外メ雲ジ ~ 183 ~ lptmr_pulse_clean 攻城略地之一天攻破 K60/KL26 —— 松飞科技 清空计数值 枚举详解 枚举定义 LPT0_ALTn 1 /** 2 * @brief LPTMR 脉冲计数输入管脚选项 3 */ 4 typedef enum 5{ 6 //只有 1、2 管脚,并没有 0、3 管脚 7 LPT0_ALT1 = 1, // PTA19 8 LPT0_ALT2 = 2 // PTC5 9 } LPT0_ALTn; 枚举作用 用来定义 LPTMR 脉冲计数输入管脚选项 枚举定义 LPT_CFG 1 /** 2 * @brief LPTMR 脉冲计数触发方式 3 */ 4 typedef enum LPT_CFG 5{ 6 LPT_Rising = 0, //上升沿触发 7 LPT_Falling = 1 //下降沿触发 8 } LPT_CFG; 枚举作用 用来定义 LPTMR 脉冲计数触发方式 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 184 ~ 函数详解 攻城略地之一天攻破 K60/KL26 —— 松飞科技 lptmr_delay_ms / lptmr_delay_us 函数原型 1 /* 2 void 3 void 用于延时 */ lptmr_delay_ms(uint16 ms); lptmr_delay_us(uint16 us); //延时(ms) //延时(us) 功能说明 LPTMR 延时函数,不需要初始化就可以直接调用。 调用例子 1 lptmr_delay_ms(32); // LPTMR 延时 32ms 山外 lptmr_timing_ms / lptmr_timing_us 函数原型 1 /* 2 void 3 void 用于定时 */ lptmr_timing_ms(uint16 ms); lptmr_timing_us(uint16 ms); //定时(ms) //定时(us) 功能说明 LPTMR 定时函数,定时时间到了会触发中断。 调用例子 1 lptmr_timing_ms(32); 2 set_vector_handler(LPTimer_VECTORn,lptmr_hander); 3 enable_irq(LPTimer_IRQn); // LPTMR 定时 32ms // 设置中断服务函数到中断向量表里 // 使能 LPTMR 中断 lptmr_time_start_ms / lptmr_time_start_us 函数原型 1 /* 2 void 3 void 用于计时 */ lptmr_time_start_ms(void); lptmr_time_start_us(void); //开始计时(ms) //开始计时(ns) 山外论坛 http://vcan123.com 山外メ雲ジ ~ 185 ~ 功能说明 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 LPTMR 开始计时(不同的开始计时单位,需要配套相应不同的获取计时时间单位 函数)。 调用例子 1 void my_delay(uint32 time) 2{ 3 volatile uint32 i = time; 4 while(i--); 5 6} 7 8 9 lptmr_time_start_ms(); 10 11 my_delay(600000); 12 i = lptmr_time_get_ms(); lptmr_time_start_ms,单位是 ms,所以这里获取计数时间也要 ms。 13 if(i == ~0) 14 { 15 printf("\n 计时时间超时"); 16 } 17 else 18 { 19 printf("\n 计时时间为:%dms",i); 20 } lptmr_time_get_ms / lptmr_time_get_us 函数原型 1 uint32 lptmr_time_get_ms(void); 2 uint32 lptmr_time_get_us(void); //获取计时时间 //获取计时时间 功能说明 获取计数时间(不同的开始计时单位,需要配套相应不同的获取计时时间单位函 数)。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 186 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 调用例子 参考 lptmr_time_start_ms / lptmr_time_start_us 的调用例子。 函数原型 lptmr_time_close 1 void lptmr_time_close(); 功能说明 关闭 LPTMR 计时 //关闭计时器 山外 函数原型 lptmr_pulse_init 1 void lptmr_pulse_init (LPT0_ALTn, uint16 count, LPT_CFG); //脉冲计数器初始化设置 功能说明 LPTMR 脉冲计数初始化 调用例子 可配置计数满多少 就触发中断。 最大为 0xFFFF 1 lptmr_pulse_init(LPT0_ALT1,0xFFFF,LPT_Rising); // LPTMR 脉冲计数,计数 0xFFFF 后触发中断请求(需要开中断才执行中断服务函数),上升沿捕捉 函数原型 lptmr_pulse_get 1 uint16 lptmr_pulse_get (void); 功能说明 LPTMR 获取计数值 调用例子 1 uint16 data = lptmr_pulse_get(); //获取脉冲计数值 //获取计数值 函数原型 lptmr_pulse_clean 1 void lptmr_pulse_clean (void); //清空计数值 山外论坛 http://vcan123.com 山外メ雲ジ ~ 187 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 功能说明 清空 LPTMR 脉冲计数 调用例子 1 lptmr_counter_clean(); //清空 LPTMR 脉冲计数 山外 LPTMR 测试例程 LPTMR_DELAY 延时测试例程 本例程,我们用 LED 闪烁,中间插入 LPTMR 延时函数,通过 LED 灯的闪烁速度 从而校验 LPTMR 定时器是否准确。 烧录本例程后,可以看到 LED 灯闪烁频率为 2Hz。 1 /*! 2 * @brief 3 * @since 4 * @note main 函数 v5.0 山外 LPTMR 延时实验 5 */ 6 void main() 7{ 8 led_init(LED0); 9 10 while(1) 11 { 12 led_turn(LED0); 13 14 lptmr_delay_ms( 1000); 15 } 16 } //初始化 LED //反转 LED0 状态,使得 LED0 闪烁 //使用 LPTMR 延时: 1000ms LPTMR_TIMING 定时中断测试例程 LPTMR 定时中断,设定 1 秒进行一次中断,中断里闪烁 LED 灯。即 LED 灯闪烁 频率为 2Hz。 LPTMR 定时中断测试例程: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 188 ~ 1 //函数声明 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 2 extern void LPTMR_IRQHandler(void); 3 4 /*! 5 * @brief main 函数 6 * @since v5.0 7 * @note 山外 LPTMR 定时中断实验 8 */ 9 void main() 10 { 11 led_init(LED0); //初始化 LED0,LPTMR 中断用到 LED0 12 13 lptmr_timing_ms(1000); //初始化 LPTMR,定时时间为: 1000ms 14 set_vector_handler(LPTMR_VECTORn ,LPTMR_IRQHandler); 15 enable_irq (LPTMR_IRQn); //设置 LPTMR 的中断服务函数为 LPTMR_IRQHandler //使能 LPTMR 中断 16 17 while(1) 18 { 19 //这里不需要执行任务,等待中断来闪烁 LED0 20 } 21 } 22 23 /*! 24 * @brief LPTMR 中断服务函数 25 * @since v5.0 26 */ 27 void LPTMR_IRQHandler(void) 28 { 29 led_turn(LED0); //闪烁 LED0 30 31 LPTMR_Flag_Clear(); //清中断标志位 32 } LPTMR_TIME 计时测试例程 本例程,我们直接利用其他定时器来作为延时,然后 LPTMR 测延时时间是多少, 从而可以校验 LPTMR 计时时间是否准确。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 189 ~ 1 /*! 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 2 * @brief main 函数 3 * @since v5.0 4 * @note 山外 LPTMR 计时实验 5 */ 6 void main() 7{ 8 uint32 timevar; 9 while(1) 10 { 11 lptmr_time_start_ms(); //开始计时 12 13 DELAY_MS(50); //延时一段时间(由于语句执行需要时间,因而实际的延时时间会更长一些) 14 15 timevar = lptmr_time_get_ms(); //停止计时,获取计时时间 16 17 printf("\n\n 计时时间为:%dms",timevar); //打印计时时间 18 19 DELAY_MS(1000); 20 } 21 } 编译下载后,可以看到实验效果: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 190 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 LPTMR_PULSE 脉冲计数测试例程 本例程,我们直接利用 K60_FTM/KL26_TPM 产生 PWM 脉冲,然后用 LPTMR 模 块来间隔一段时间测脉冲数。 K60 代码,用 FTM0_CH4(PTA7)来产生一个 10K 频率的 PWM 波,延时 1000 毫秒,然后测这段时间内的脉冲数是多少。用的 LPTMR 输入管脚为 LPT0_ALT1 (PTA19)。 1 /*! 2 * @brief main 函数 3 * @since v5.0 4 * @note 山外 LPTMR 脉冲计数实验,需要短接 PTA7 和 PTA19 5 */ 6 void main(void) 7{ 8 #define INT_COUNT 0xFFFF //LPT 产生中断的计数次数 9 10 uint16 count; 11 12 ftm_pwm_init(FTM0, FTM_CH4, 10000, 50); //FTM 模块产生 PWM,用 FTM0_CH4 ,即 PTA7 ,频率为 10000,占空比 50% 13 //修改频率,验证 不同 PWM 下计数值是多少。 14 15 lptmr_pulse_init(LPT0_ALT1, INT_COUNT, LPT_Rising); //初始化脉冲计数器,用 LPT0_ALT1,即 PTA19 输入,每隔 INT_COUNT 产生中断 //(需要开中断才能产生中断),上升沿触发 16 17 while(1) 18 { 19 lptmr_pulse_clean(); //清空脉冲计数器计算值(马上清空,这样才能保证计数值准确) 20 21 DELAY_MS(1000); //利用 延时时间,LPTMR 模块进行 计算,累加 FTM 产生的 PWM 脉冲 22 23 count = lptmr_pulse_get(); //保存脉冲计数器计算值 24 25 printf("LPTMR 脉冲计数为:%d\n", count); //打印计数值 26 } 山外论坛 http://vcan123.com 山外メ雲ジ ~ 191 ~ 27 } 攻城略地之一天攻破 K60/KL26 —— 松飞科技 上位机实验效果: 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 192 ~ ADC 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K60 的 ADC 模块有 ADC0 和 ADC1,而 KL26 的 ADC 模块仅有 ADC0。两者的用 法都是相同的。 快速入门:ADC 库使用方法 形参变量的命名及其作用 山外论坛 http://vcan123.com 山外メ雲ジ ~ 193 ~ 形参变量 ADCn_e ADCn_Ch_e 攻城略地之一天攻破 K60/KL26 —— 松飞科技 K60 ADC 形参变量的命名及其作用 作用 取值 ADC 模块 ADC0, ADC1 号 ADC 通道 // ---------------------------------ADC0------------------------- ADC0_DP0, 直接 ADC0_DP0 等这类管脚。 ADC0_DP1, PGA0_DP, //不支持 ADC,需要配置 ADC PGA register 设置放大增益 ADC0_DP3, 山外 //软件触发不支持 B 通道 ADC0_SE4b, // PTC2 ADC0_SE5b, // PTD1 ADC0_SE6b, // PTD5 ADC0_SE7b, // PTD6 不支持软件 ADC 不支持软件 ADC 不支持软件 ADC 不支持软件 ADC A、B 通道,跟数字后缀的一 样,都是用来区分不同的通 道名字而已。 而与数字的不同,A 通道用在 软件触发,B 通道用在硬件触 发。 ADC0_SE8, // PTB0 ADC0_SE9, // PTB1 ADC 软件触发所支 ADC0_SE10, // PTA7 持的通道 ADC0_SE11, // PTA8 ADC0_SE12, // PTB2 ADC0_SE13, // PTB3 ADC0_SE14, // PTC0 ADC0_SE15, // PTC1 ADC0_SE16, // ADC0_SE16 ADC0_SE17, // PTE24 ADC0_SE18, // PTE25 ADC0_DM0, // ADC0_DM0 ADC0_DM1, // ADC0_DM1 RES0, // 保留 RES1, // 保留 DAC0_OUT, // DAC0 输出 不支持 ADC RES2, // 保留 RES3, // 保留 Temp0_Sensor, // Temperature Sensor,内部温度测量,可用 ADC 函数 Bandgap0, RES4, // 温度补偿结构带隙基准源 不支持 ADC // 保留 温度传感器 VREFH0, // 参考高电压,可用 ADC 函数 ,结果恒为 2^n-1 VREFL0, // 参考低电压,可用 ADC 函数 ,结果恒为 0 Module0_Dis, // 不支持 ADC B 通道不支持软件触发。 ADC 例程用的都是软件 ADC 方式。 // ---------------------------------ADC1------------------------ADC1_DP0, ADC1_DP1, PGA1_DP, // 不支持 ADC ADC1_DP3, ADC1_SE4a, // PTE0 ADC1_SE5a, // PTE1 ADC1_SE6a, // PTE2 ADC1_SE7a, // PTE3 ADC1_SE4b = ADC1_SE4a, // PTC8 不支持软件 ADC ,传递 ADC1_SE4b 到软件 ADC 函数里,会当作 ADC1_SE4a 处理。 ADC1_SE5b = ADC1_SE5a, // PTC9 不支持软件 ADC ADC1_SE6b = ADC1_SE6a, // PTC10 不支持软件 ADC ADC1_SE7b = ADC1_SE7a, // PTC11 不支持软件 ADC ADC1_SE8, ADC1_SE9, ADC1_SE10, ADC1_SE11, ADC1_SE12, ADC1_SE13, // PTB0 // PTB1 // PTB4 // PTB5 // PTB6 // PTB7 山外论坛 http://vcan123.com 山外メ雲ジ ~ 194 ~ ADC_nbit 字符 攻城略地之一天攻破 K60/KL26 —— 松飞科技 ADC1_SE14, // PTB10 ADC1_SE15, // PTB11 ADC1_SE16, // ADC1_SE16 ADC1_SE17, // PTA17 VREF_OUTPUT, // VREF Output ADC1_DM0, // ADC1_DM0 ADC1_DM1, RES5, // ADC1_DM1 //保留 RES6, DAC1_OUT, RES7, //保留 RES8, Temp1_Sensor, Bandgap1, // 温度补偿结构带隙基准源 不支持 ADC RES9, VREFH1, // 参考高电压,可用 ADC 函数 ,结果恒为 2^n-1 VREFL1, // 参考低电压,可用 ADC 函数 ,结果恒为 0 Module1_Dis, // 不支持 ADC 山外 注: 软件触发不支持 B 通道,目前库只支持软件触发。 ADC_8bit , ADC_12bit , ADC_10bit , ADC_16bit 形参变量 ADCn_e ADCn_Ch_e KL26 ADC 形参变量的命名及其作用 作用 取值 ADC 模块 ADC0 号 ADC 通道 // ---------------------------------ADC0------------------------- ADC0_DP0 = 0, // PTE20 ADC0_SE0 = 0, 两个的值相同,其实是同一个通道, ADC0_DP1 = 1, // PTE16 不同的名字而已,都是一样的。 ADC0_SE1 = 1, ADC0_DP2 = 2, // PTE18 ADC0_SE2 = 2, ADC0_DP3 = 3, // PTE22 ADC0_SE3 = 3, ADC0_DM0 = 4, // PTE21 ADC0_SE4a= 4, ADC0_DM1 = 5, // PTE17 ADC0_SE5a= 5, A、B 通道,跟数字后缀的一 样,都是用来区分不同的通 道名字而已。 而与数字的不同,A 通道用在 软件触发,B 通道用在硬件触 发。 ADC0_DM2 = 6, // PTE19 ADC0_SE6a= 6, ADC0_DM3 = 7, // PTE23 ADC0_SE7a= 7, ADC0_SE4b= 4, ADC0_SE5b= 5, ADC0_SE6b= 6, ADC0_SE7b= 7, // PTE29 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 // PTD1 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 // PTD5 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 // PTD6 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 ADC0_SE8, ADC0_SE9, ADC0_RES0, ADC0_SE11, ADC0_SE12, ADC0_SE13, // PTB0 // PTB1 // 保留 // PTC2 // PTB2 // PTB3 山外论坛 http://vcan123.com 山外メ雲ジ ~ 195 ~ ADC_nbit 字符 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 ADC0_SE14, // PTC0 ADC0_SE15, // PTC1 ADC0_RES1, // 保留 ADC0_RES2, // 保留 ADC0_RES3, // 保留 ADC0_RES4, // 保留 ADC0_RES5, // 保留 ADC0_RES6, // 保留 ADC0_RES7, // 保留 ADC0_SE23, // PTE30 DAC0_OUT = ADC0_SE23, // PTE30 DAC0 输出 ,传入 ADC 函数会当作 ADC0_SE23 处理 ADC0_RES8, // 保留 ADC0_RES9, // 保留 Temp0_Sensor, // Temperature Sensor,内部温度测量,可用 ADC 函数 Bandgap0, // 温度补偿结构带隙基准源 不支持 ADC ADC0_RES10, // 保留 VREFH0, // 参考高电压,可用 ADC 函数 ,结果恒为 2^n-1 VREFL0, // 参考低电压,可用 ADC 函数 ,结果恒为 0 Module0_Dis, // 不支持 ADC 注: 软件触发不支持 B 通道,目前库只支持软件触发。 ADC_8bit , ADC_12bit , ADC_10bit , ADC_16bit 常用枚举列表 ADCn ADC_Ch ADC_nbit 名称 ADC 模块号 ADC 通道 精度位数 功能说明 函数列表 adc_init ad_once ad_mid ad_ave adc_start 函数名称 adc_stop 函数功能 初始化 ADC 模块 采样一次一路模拟量的 AD 值 采样三次取中值 采样 N 次的 ADC 值平均值 开始 adc 转换 停止 ADC 模块的 AD 转换,同一个模块的 所有通道都会停止。 枚举详解 枚举定义 1. //ADC 端口 ADCn 山外论坛 http://vcan123.com 山外メ雲ジ ~ 196 ~ 2. typedef enum ADCn 3. { 4. ADC0, 5. ADC1 6. }ADCn; 攻城略地之一天攻破 K60/KL26 —— 松飞科技 枚举作用 用来定义 ADC 模块号 山外 ADCn_Ch_e 枚举定义 K60 和 KL26 的通道定义有所不同。具体定义如下: K60 1 typedef enum 2{ 3 // ---------------------------------ADC0------------------------- 4 ADC0_DP0, 5 ADC0_DP1, 6 PGA0_DP, //不支持 ADC,需要配置 ADC PGA register 设置放大增益 7 ADC0_DP3, 8 //软件触发不支持 B 通道 9 ADC0_SE4b, // PTC2 不支持软件 ADC 10 ADC0_SE5b, // PTD1 不支持软件 ADC 11 ADC0_SE6b, // PTD5 不支持软件 ADC 12 ADC0_SE7b, // PTD6 不支持软件 ADC 13 14 ADC0_SE8, // PTB0 15 ADC0_SE9, // PTB1 16 ADC0_SE10, // PTA7 17 ADC0_SE11, // PTA8 18 ADC0_SE12, // PTB2 19 ADC0_SE13, // PTB3 20 ADC0_SE14, // PTC0 21 ADC0_SE15, // PTC1 22 ADC0_SE16, // ADC0_SE16 23 ADC0_SE17, // PTE24 24 ADC0_SE18, // PTE25 25 ADC0_DM0, // ADC0_DM0 山外论坛 http://vcan123.com 山外メ雲ジ ~ 197 ~ 26 ADC0_DM1, 攻城略地之一天攻破 K60/KL26 —— 松飞科技 // ADC0_DM1 山外 27 RES0, 28 RES1, 29 DAC0_OUT, 30 RES2, 31 RES3, // 保留 // 保留 // DAC0 输出 不支持 ADC // 保留 // 保留 32 Temp0_Sensor, // Temperature Sensor,内部温度测量,可用 ADC 函数 33 Bandgap0, // 温度补偿结构带隙基准源 不支持 ADC 34 RES4, // 保留 35 VREFH0, // 参考高电压,可用 ADC 函数 ,结果恒为 2^n-1 36 VREFL0, // 参考低电压,可用 ADC 函数 ,结果恒为 0 37 Module0_Dis, // 不支持 ADC 38 39 // ---------------------------------ADC1------------------------- 40 ADC1_DP0, 41 ADC1_DP1, 42 PGA1_DP, // 不支持 ADC 43 ADC1_DP3, 44 ADC1_SE4a, // PTE0 45 ADC1_SE5a, // PTE1 46 ADC1_SE6a, // PTE2 47 ADC1_SE7a, // PTE3 48 49 ADC1_SE4b = ADC1_SE4a, // PTC8 不支持软件 ADC ,传递 ADC1_SE4b 到软件 ADC 函数里,会当作 ADC1_SE4a 处理。 50 ADC1_SE5b = ADC1_SE5a, // PTC9 51 ADC1_SE6b = ADC1_SE6a, // PTC10 52 ADC1_SE7b = ADC1_SE7a, // PTC11 不支持软件 ADC 不支持软件 ADC 不支持软件 ADC 53 54 ADC1_SE8, // PTB0 55 ADC1_SE9, // PTB1 56 ADC1_SE10, // PTB4 57 ADC1_SE11, // PTB5 58 ADC1_SE12, // PTB6 59 ADC1_SE13, // PTB7 60 ADC1_SE14, // PTB10 61 ADC1_SE15, // PTB11 62 ADC1_SE16, // ADC1_SE16 63 ADC1_SE17, // PTA17 山外论坛 http://vcan123.com 山外メ雲ジ ~ 198 ~ 64 VREF_OUTPUT, 65 ADC1_DM0, 66 ADC1_DM1, 67 RES5, 68 RES6, 69 DAC1_OUT, 70 RES7, 71 RES8, 72 Temp1_Sensor, 73 Bandgap1, 74 RES9, 75 VREFH1, 76 VREFL1, 77 Module1_Dis, 78 79 } ADCn_Ch_e; 攻城略地之一天攻破 K60/KL26 —— 松飞科技 // VREF Output // ADC1_DM0 // ADC1_DM1 //保留 //保留 // 温度补偿结构带隙基准源 不支持 ADC // 参考高电压,可用 ADC 函数 ,结果恒为 2^n-1 // 参考低电压,可用 ADC 函数 ,结果恒为 0 // 不支持 ADC KL26 1 typedef enum 2{ 3 // ---------------------------------ADC0------------------------- 4 ADC0_DP0 = 0, // PTE20 5 ADC0_SE0 = 0, 6 7 ADC0_DP1 = 1, // PTE16 8 ADC0_SE1 = 1, 9 10 ADC0_DP2 = 2, // PTE18 11 ADC0_SE2 = 2, 12 13 ADC0_DP3 = 3, // PTE22 14 ADC0_SE3 = 3, 15 16 ADC0_DM0 = 4, // PTE21 17 ADC0_SE4a= 4, 18 19 ADC0_DM1 = 5, // PTE17 20 ADC0_SE5a= 5, 山外论坛 http://vcan123.com 山外メ雲ジ 山外 ~ 199 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 21 22 ADC0_DM2 = 6, // PTE19 23 ADC0_SE6a= 6, 24 25 ADC0_DM3 = 7, // PTE23 26 ADC0_SE7a= 7, 27 28 ADC0_SE4b= 4, // PTE29 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 29 30 ADC0_SE5b= 5, // PTD1 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 31 32 ADC0_SE6b= 6, // PTD5 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 33 34 ADC0_SE7b= 7, // PTD6 不支持软件 ADC,传递进软件触发 ADC,会当作 a 通道处理 35 36 ADC0_SE8, // PTB0 37 38 ADC0_SE9, // PTB1 39 40 ADC0_RES0, // 保留 41 42 ADC0_SE11, // PTC2 43 44 ADC0_SE12, // PTB2 45 46 ADC0_SE13, // PTB3 47 48 ADC0_SE14, // PTC0 49 50 ADC0_SE15, // PTC1 51 52 ADC0_RES1, // 保留 53 54 ADC0_RES2, // 保留 55 56 ADC0_RES3, // 保留 57 58 ADC0_RES4, // 保留 59 山外论坛 http://vcan123.com 山外メ雲ジ 山外 ~ 200 ~ 60 ADC0_RES5, 攻城略地之一天攻破 K60/KL26 —— 松飞科技 // 保留 61 62 ADC0_RES6, // 保留 63 64 ADC0_RES7, // 保留 65 66 ADC0_SE23, // PTE30 67 68 DAC0_OUT = ADC0_SE23, // PTE30 DAC0 输出 ,传入 ADC 函数会当作 ADC0_SE23 处理 69 70 ADC0_RES8, // 保留 71 72 ADC0_RES9, // 保留 73 74 Temp0_Sensor, // Temperature Sensor,内部温度测量,可用 ADC 函数 75 Bandgap0, // 温度补偿结构带隙基准源 不支持 ADC 76 ADC0_RES10, // 保留 77 VREFH0, // 参考高电压,可用 ADC 函数 ,结果恒为 2^n-1 78 VREFL0, // 参考低电压,可用 ADC 函数 ,结果恒为 0 79 Module0_Dis, // 不支持 ADC 80 81 } ADCn_Ch_e; 注:调用时直接写模块” _”后面的通道号就可以。如 SE16、SE4a 等。 软件触发不支持 B 通道,目前库只支持软件触发。 枚举作用 用来定义 ADC 通道。 山外 枚举定义 1. //精度位数 2. typedef enum ADC_nbit 3. { 4. ADC_8bit =0x00, 5. ADC_10bit =0x02, 6. ADC_12bit =0x01, 7. ADC_16bit =0x03 8. }ADC_nbit; ADC_nbit 值是根据寄存器配置来 定,非任意取值。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 201 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 枚举作用 用来定义 ADC 模数转换的精度。 山外 函数详解 函数原型 adc_init 1 void adc_init 功能说明 ADC 初始化 调用例子 (ADCn_Ch_e); //ADC 初始化 1 adc_init (ADC0_SE10 ); //初始化 ADC0_SE10 ,K60 使用 PTA7 管脚 函数原型 ad_once 1 uint16 adc_once (ADCn_Ch_e, ADC_nbit); 功能说明 采样一次一路模拟量的 AD 值 //采集一次一路模拟量的 AD 值 函数返回 返回采样数值。按采集精度来返回不同大小,16 位精度最大返回:65535 调用例子 1 uint16 var = adc_once(ADC0_SE10, ADC_8bit); 函数原型 adc_stop 1 void adc_stop (ADCn_e); //停止 ADC 转换 功能说明 停止 ADC 模块的 AD 转换,同一个模块的所有通道都会停止。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 202 ~ 调用例子 1 adc_stop(ADC0); 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 ADC 测试例程 ADC 可用于测量模拟信号的电压。K60 和 KL26 的用法都是相同的。本例程就以 山外 K60 开发板为例,利用滑动变阻器分压来产生不同的电压,然后单片机 ADC 采 集测量电压。可利用万用表来滑动变阻器的分压值,并与单片机采集到的值做对比, 滑动变阻 短接这两个管 滑动变阻器 需要短接 ADC1_SE16 和滑动 变阻器这两个管脚 短接滑动变阻器引脚和 ADC1_SE16,由 ADC1_SE16 通道管脚来获取 ADC 值。 1 /*! 2 * @brief main 函数 山外论坛 http://vcan123.com 山外メ雲ジ ~ 203 ~ 3 * @since v5.1 攻城略地之一天攻破 K60/KL26 —— 松飞科技 4 * @note 山外 ADC 实验 5 */ 6 void main() 7{ 8 uint16 var; 9 adc_init(ADC1_SE16); //ADC 初始化 10 11 while(1) 12 { 13 var = adc_once (ADC1_SE16, ADC_8bit); 14 printf("\nADC 采样结果为:%d",var); 15 printf(" 相应电压值为%dmV",(3300*var)/((1<<8)-1)); 16 17 DELAY_MS(500); 18 19 } 20 } 实验 结果如下: 山外 转动滑动变阻器,可看到采集到的 ADC 值发生变化。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 204 ~ DAC 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K60 有 2 个独立的 12 位 DAC 模块,KL26 只有 1 个独立的 12 位 DAC 模块;两个 可选参考电压:VREF_OUT (内部参考电压,连接到 DACREF_1)和 VDDA(3.3v, 连接到 DACREF_2)。 常见的 DAC 模块原理图: 参考电压 源选择 电子开关,用寄存器 DACDAT 来控制开关 电压跟随 器,做缓冲 产生中断 条件 缓冲区,用来保 存转换的数据 山外论坛 http://vcan123.com 山外メ雲ジ ~ 205 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 由上图可以知道,把数据放入 DAC 的数据寄存器,即 DACx_DAT 中的 DACDATA[11:0]位,那样就可以把数字量转换为模拟电压。 输出电压 Vout = Vin * (1 + DACDAT0[11:0])/4096 山外 也可以把数据放入缓冲区,由硬件触发源来触发转换。 缓冲区大小为 16 双字节(即 32B),有三种工作模式来读取缓冲区: ①正常模式,把缓冲区作为循环缓冲区来读取,每触发一次 DAC 转换,读指针加 1。每当读指针到顶部时,返回底部重新往上读。 ②摆动模式,当读指针到顶部时,不再返回底部,而是每触发一次 DAC 转换,读 指针减 1。 ③一次扫描模式,每触发一次 DAC 转换,读指针加 1。每当读指针到顶部时,就 停止转换,读指针保存不变。 读指针 DACBFRP 复位是为 0。顶部指针 DACBFUP 复位是为 15。底部指针就是 0。 转换时,读指针 指向 正在转换的数据,即可以通过访问读指针来查询当前正在 转换的数据。 读指针从底部到顶部读取,在中间可以设置水位标志位(DAC buffer watermark) 作为预警作用,水位标志位选择范围为离栈顶 1~4 双字节。当读指针指向水位标志位 时,DACBFWMF 置 1,可设置产生中断请求。 当读指针指向底部、水位标志位、顶部时,可以设置产生中断请求。 举个例子 当 DACBFUP 顶部指针 为 9,DACBFWM 水位标志为 2,DACBFRP 读指针指向 2 时,DACBBIEN、DACBTIEN、DACBWIEN 都置 1,即对应为 允许缓冲区置底中断、 允许缓冲区置顶中断、允许水位标志中断 如下面图框: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 206 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 最大顶部 15 14 13 12 11 DACBFUP 顶部指针 10 当读指针指到相应的 9 位,可设置产生中断 8 DACBFWM 水位标志 7 6 触发中断 5 4 3 2 DACBFRP 读指针 1 底部 0 ①② ③ 读指针的工作方式:①为正常模式; ②为摆动模式;③为一次扫描模式。 快速入门:DAC 库使用方法 形参变量的命名及其作用 形参变量 作用 DACn DAC 模块号 DAC0、DAC1 val 数字量 12bit 本库提供的代码仅支持 VREF_OUT 参考电压,且不使用缓冲区。 取值 KL26 只有一个 DAC,故 KL26 此处仅可选 DAC0 常用枚举列表 DACn 名称 DAC 模块号 山外论坛 http://vcan123.com 功能说明 山外メ雲ジ ~ 207 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 函数列表 dac_init dac_out 函数名称 函数功能 DAC 初始化 DAC 一次转换操作 枚举详解 枚举定义 1 typedef enum DACn 2{ 3 DAC0, 4 5 DAC_MAX 6 } DACn_e; //DAC 模块 枚举作用 用来定义 DAC 模块号 DACn 山外 函数详解 函数原型 1 void dac_init(DACn_e); 功能说明 初始化 DAC 模块 dac_init //DAC 一次转换初始化 调用例子 1 dac_init (DAC0 ); //初始化 DAC0 函数原型 dac_out 1 void dac_out(DACn_e, uint16 val); //DAC 一次转换操作 山外论坛 http://vcan123.com 山外メ雲ジ ~ 208 ~ 功能说明 DAC 一次转换操作 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 调用例子 1 dac_out (DAC0 ,0x100); //初始化 DAC0 输出 0x100 数字量对应的模拟量:3300*(1 + 0x100)/4096 mV DAC 综合测试例程 DAC 输出正弦波测试 本例程,我们根据上面提供的 DAC API 接口实现正弦波的输出。如错误!未找到 引用源。所示,需要把示波器表笔接到 DAC0_OUT 管脚,程序中控制 DAC1 输出正弦 波。 DAC0_OUT 管 DAC0_OUT 管 1 /*! 2 * @brief main 函数 山外论坛 http://vcan123.com 山外メ雲ジ ~ 209 ~ 3 * @since v5.0 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 4 * @note 山外 DAC 输出正弦波 实验 5 */ 6 void main() 7{ 8 float val = 0; 9 uint16 result; 10 11 12 dac_init(DAC0); 13 14 while(1) 15 { 16 result =(uint16) ( 纯数学知识 17 ((sin(val)+1.0)/2.0 ) //sin 的取值范围是 -1 ~ 1 ,加 1 后变成 0~2 ,再 除以 2 确保范围在 0~1 之间 18 *((1<<12) - 1) //DAC 是 12bit 19 ); 20 21 dac_out(DAC0, result); //输出 DAC ,可通过示波器看到正弦波 22 23 val += 0.1; 24 } 25 } 编译下载后,我们把示波器表笔接入到 DAC0_OUT 管脚,在示波器上可以看到 正弦波信号,如错误!未找到引用源。所示。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 210 ~ DMA 模块 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 DMA 的作用是一个专业的数据搬运工,只需要告诉它数据源地址在哪里,数据目 的地址在哪里,每次传输多少 个 数据,一共传输多少次,什么时候触发传输,它就 会根据条件来自动搬运数据,从而释放了 CPU,可以让 CPU 干其他的活。 另外,它是专业的,初始化后就可以自动地工作,所以不需要像 CPU 那样执行代 码来搬运数据,所以 DMA 传输数据的速度更快。 K60 的 DMA 共有 63 个 DMA 源(称为槽:slots),16 个独立的 DMA 通道。 KL26 的 DMA 共有 63 个 DMA 源(称为槽:slots),4 个独立的 DMA 通道。 DMA 源,即请求 DMA 传输一次的请求源,换句话说就是触发什么时候传输的触 发源。 DMA 通道,就是传输数据的线路。 假如从 A 地址过河到 B 地址,那么 A 地址就是数据源地址,B 地址就是目的地址, 有 n 条船就是 n 个通道,船长决定啥时候开船就是 DMA 请求源,每条船每次可运输 多少个人,一天可运输多少次就跟 DMA 的每次传输多少个数据,一个传输多少次类 似。 山外库目前的 DMA 例程仅提供了 GPIO 端口数据到内存的参考例程,其他的模块 需要自行配置。 快速入门:DMA 传输端口数据 形参变量的命名及其作用 形参变量 DMA_BYTEn DMA_CHn 作用 DMA 每次传输字节数 通道号 取值 K60 可选:DMA_BYTE1, DMA_BYTE2, DMA_BYTE4 , DMA_BYTE16 KL26 可选:DMA_BYTE1, DMA_BYTE2, DMA_BYTE4 K60:DMA_CH0~ DMA_CH15 KL26:DMA_CH0~ DMA_CH3 山外论坛 http://vcan123.com 山外メ雲ジ ~ 211 ~ SADDR DADDR PTxn count DMA_cfg ( K60 才 有 , KL26 没有) 攻城略地之一天攻破 K60/KL26 —— 松飞科技 源地址 目的地址 触发源引脚号 x 替换为 A~E,n 替换为 0~31 如 PTA27 DMA 传输次数 山外 DMA 传输 PORT 端口 DADDR_RECOVER 恢复目的地址、DADDR_KEEPON 目的地址保持 数据的配置 不变 常用枚举列表 DMA_cfg DMA_BYTEn DMA_CHn 名称 功能说明 DMA 传输 PORT 端口数据的配置 DMA 每次传输字节数 DMA 通道号 函数列表 函数名称 dma_portx2buff_init dma_repeat 函数功能 初始化 PORTx 传输到缓冲区代码 重新加载传输参数(避免重新调用初始化 函数) 宏定义列表 宏定义名词 DMA_IRQ_EN DMA_IRQ_DIS DMA_IRQ_CLEAN DMA_EN DMA_DIS 功能说明 允许 DMA 通道传输 禁止 DMA 通道传输 清除通道传输中断标志位 使能通道硬件 DMA 请求 禁止通道硬件 DMA 请求 枚举详解 枚举定义 1 typedef enum 2{ DMA_cfg 山外论坛 http://vcan123.com 山外メ雲ジ ~ 212 ~ 3 DADDR_RECOVER = 0, 4 DADDR_KEEPON = 1, 5 } DMA_cfg; 攻城略地之一天攻破 K60/KL26 —— 松飞科技 //DMA 传输完成后恢复目的地址 //DMA 传输完成后目的地址保持不变 枚举作用 用来定义 DMA 的相关配置。KL26 中无此选项。 山外 枚举定义 K60 DMA_BYTEn KL26 1 typedef enum //DMA 每次传输字节数 2{ 3 DMA_BYTE1 = 0, 4 DMA_BYTE2 = 1, 5 DMA_BYTE4 = 2, 6 DMA_BYTE16 = 4 7 } DMA_BYTEn; 8 1 typedef enum //DMA 每次传输字节数 2{ 3 DMA_BYTE1 = 1, 4 DMA_BYTE2 = 2, 5 DMA_BYTE4 = 0, 6 } DMA_BYTEn; 7 枚举作用 用来定义 DMA 每次传输字节数 。 每个枚举参量的值都是根据寄存器配置来配置,非任意的。 枚举定义 1 typedef enum 2{ 3 DMA_CH0, 4 DMA_CH1, 5 DMA_CH2, 6 DMA_CH3, 7 DMA_CH4, 8 DMA_CH5, 9 DMA_CH6, 10 DMA_CH7, 11 DMA_CH8, 12 DMA_CH9, 13 DMA_CH10, 14 DMA_CH11, 15 DMA_CH12, 16 DMA_CH13, 17 DMA_CH14, 18 DMA_CH15 19 } DMA_CHn; K60 DMA_CHn 1 typedef enum 2{ 3 DMA_CH0, 4 DMA_CH1, 5 DMA_CH2, 6 DMA_CH3, 7 } DMA_CHn; KL26 山外论坛 http://vcan123.com 山外メ雲ジ ~ 213 ~ 枚举作用 DMA 通道号。 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 函数详解 函数原型 dma_portx2buff_init K60 KL26 1 void dma_portx2buff_init(DMA_CHn, void *SADDR, void *DADDR, PTXn_e, DMA_BYTEn, uint32 count, uint32 cfg); 1 void dma_portx2buff_init(DMA_CHn, void *SADDR, void *DADDR, PTXn_e, DMA_BYTEn, uint32 count); 功能说明 K60 比 KL26 多了配置选项 初始化 DMA,使得 PORT 端口数据通过 DMA 传输到 BUFF 缓冲区 注意:用到的输入端口和触发端口已经内部初始化,不需要再额外初始化。默认触发端口为上升沿触发,如 果需要配置为下降沿触发,可调用 port_init 函数来重新配置。 源地址是 GPIO 的输入寄存器的地址,一般填入:(void * )&PTx_Bn_IN 或 (void * )&PTx_Wn_IN 或 (void * )&PTA_IN。 调用例子 1 uint8 buff[10]; 2 dma_portx2buff_init(DMA_CH0, (void *)&PTB_B0_IN, (void *)buff, PTA7, DMA_BYTE1, 10, DADDR_RECOVER); 3 //DMA 初始化,源地址:PTB_B0_IN,目的地址:buff,PTA7 触发(默认上升沿),每次传输 1 字节,共传输 10 次 ,传输 结束后恢复地址 4 5 port_init(PTA7,ALT1 | DMA_FALLING); //默认触发源是上升沿,此处改为 下降沿触发 6 7 DMA_EN(DMA_CH0); //需要使能 DMA 后才能传输数据 函数原型 dma_repeat 1 void dma_repeat(DMA_CHn CHn,void *SADDR, void *DADDR,uint32 count) 功能说明 DMA 重新配置,传输完毕后,一些参数会改变,需要重新赋值。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 214 ~ 调用例子 攻城略地之一天攻破 K60/KL26 —— 松飞科技 1 dma_repeat(DMA_CH0, (void *)&PTE_B2_IN, (void *)img_buff,CAMERA_DMA_NUM); 宏定义 DMA_IRQ_EN 功能说明 允许 DMA 通道传输完成中断 调用例子举例 1 DMA_IRQ_EN(DMA_CH3); //允许 DMA 通道 3 传输完成中断 DMA_IRQ_DIS 功能说明 禁止 DMA 通道传输完成中断 调用例子举例 1 DMA_IRQ_DIS(DMA_CH3); //禁止 DMA 通道 3 传输完成中断 DMA_IRQ_CLEAN 功能说明 清除通道传输中断标志位 调用例子举例 1 DMA_IRQ_CLEAN(DMA_CH3); //清除 DMA 通道 3 传输中断标志位 功能说明 使能通道硬件 DMA 请求 调用例子举例 DMA_EN 1 DMA_EN(DMA_CH3); //使能 DMA 通道 3 硬件 DMA 请求 山外 山外论坛 http://vcan123.com 山外メ雲ジ ~ 215 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 功能说明 禁止通道硬件 DMA 请求 DMA_DIS 调用例子举例 1 DMA_DIS(DMA_CH3); //禁止 DMA 通道 3 硬件 DMA 请求 山外 DMA 测试例程 DMA 传输 GPIO 数据实验 K60 和 KL26 的 DMA 函数接口基本相同,这里我就以 K60 的 DMA 为例。 K60 的 DMA 例程,采用 PTA7 作为 DMA 的请求源,上升沿触发 DMA 请求(默认) 把 PTB0~PTB7 共 8 位数据传输到数组 BUFF 里。 为了方便可控输入脉冲到 PTA7 端口,因而本例程采用串口来控制 PTA6 输出脉 冲,需要短接 PTA6 和 PTA7: 短接位置 例程中的数据口 PTB0~PTB7,是需要我们手动杜邦线接线,手动控制输入的数 据: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 216 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 GND PTB0~PTB7 山外 VCC 1 #define DMA_COUNT 10 2 3 uint8 BUFF[DMA_COUNT + 1]; //缓存数组,预多一个 4 5 /*! 6 * @brief main 函数 7 * @since v5.0 8 * @note 山外 DMA 实验 ,需要短接 PTA6 和 PTA7 ,串口助手发送数据控制传输 9 */ 10 void main(void) 11 { 12 uint8 i; 13 char command; PTA6 作为控制口,产 生触发信号。 14 15 gpio_init (PTA6, GPO, LOW); 16 初始化 DMA 模块 //初始化 PTA6 为输出低电平 17 dma_portx2buff_init (DMA_CH0, (void *)&PTB_B0_IN, BUFF, PTA7, DMA_BYTE1, DMA_COUNT, DADDR_RECOVER); 18 //DMA 初始化,源地址:PTB_B0_IN,目的地址:buff,PTA7 触发(默认上升沿),每次传输 1 字节,共传输 DMA_COUNT 次 ,传输结束后恢复地址 19 20 port_init_NoALT(PTA7,DMA_FALLING ); 修改为下降沿触发 DMA 传输。 21 22 DMA_EN(DMA_CH0); //使能 DMA 硬件请求 23 24 while(1) 25 { 使能硬件请 求 26 uart_getchar(VCAN_PORT,&command); //通过串口来控制 PTA6 的输出,即 PTA7 的输入 27 山外论坛 http://vcan123.com 山外メ雲ジ ~ 217 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 28 //产生脉冲 29 PTA6_T = 1; //取反 30 DELAY_MS(1); 31 PTA6_T = 1; //取反 串口控制来产生脉冲 32 33 34 //打印 BUFF 的缓冲区数据(便于用户看到数组内容的变化) 35 printf("\nDMA 触发后 BUFF[%d]={",DMA_COUNT); 36 for(i = 0;i < DMA_COUNT ; i++) 37 { 38 printf("%d,",BUFF[i]); 39 } 40 printf("%d};",BUFF[DMA_COUNT]); 41 产生一次脉冲就会触发 DMA 传 输,这里串口打印数组里的数 据,这样用户就知道是否采集 到数据。 42 } 43 } 山外 编译烧录程序后,打开串口助手,串口助手里每发送任何一个字符即可产生一个 PTA6 脉冲。由于 PTA6 和 PTA7 短接了,从而触发 PTA7 产生 DMA 请求,产生一次 DMA 传输,然后通过串口里显示结果: 发送了 11 次,可看到上 面共接收 11 行的数据 操作步骤和对应效果如下: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 218 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 第一次:PTB0~PTB7 悬空,直接串口发送一个字符,DMA 触发采集到 0。由于 初始化值全部为 0,因而看不出效果。 第二次:PTB1 接 3.3V 管脚,可见 DMA 触发采集回来是 2,放在第一次采集的 0 后面,可见 DMA 的目的地址每次传输都会增加 1。 第三次:PTB2 接 3.3V 管脚,采集回来 4。 …… 第十次:PTB0 接 3.3V 管脚,采集回来 1。此时,DMA 传输次数已经完成,DMA 硬件请求已经关闭,再发送任何字符也不会触发 DMA 传输,因而第十一次时结果与 第十次相同。 从本例程可以看到,由于 DMA 的使用,我们的程序里不再轮询 PTA7 的电平,不 再不停地读取 PTB0~PTB7 的电平,一切都交由 DMA 自行处理,简化了我们的代码, 也提高了效率。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 219 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 K60_SDHC 模块(带文件系统 FatFs R0.09) KL26 是没有 SDHC 模块的,只能用 SPI 来驱动 SD 卡。我们目前没弄 SPI SD 卡 例程。 SD 卡模块,直接跑文件系统 FatFs R0.09 (目前最新版本),跑文件系统,可以 使得操作变得简单,具体的说明,可以看下面的相关网页,这里仅做简单介绍。 SDHC 模块初始化代码是在 f_open 里由 FATFS 库来调用的。 FatFs 与 Petit FatFs 的介绍 被裁剪微型化的 文件系统 目前最新版本 介绍 特点 相关网页 FatFs FatFs R0.09 FatFS 是一个为小型嵌入式系统设计的通 用 FAT(File Allocation Table)文件系统模块。 FatFs 的编写遵循 ANSI C,并且完全与磁盘 I/O 层分开。因此,它独立(不依赖)于硬件架 构。它可以被嵌入到低成本的微控制器中,如 AVR, 8051, PIC, ARM, Z80, 68K 等等,而不需 要做任何修改。  兼容 Windows 的 FAT 文件系统。  独立于平台,便于移植。  非常小的代码和内存空间  各种配置选项:  多卷(物理驱动和分区)。  多个 ANSI / OEM 代码页,包括双字 节字符。  在 ANSI/OEM 或 Unicode 下,支持 长文件名  支持实时系统  支持多个扇区的大小。  支持只读、减少 API 接口、I/O 缓冲 区等 elm-chan.org/fsw/ff/00index_e.html Petit FatFs Petit FatFs R0.02a Petit FatFs 是一个裁剪过的 FatFS 模 块,应用在微型的 8 位微控制器上。遵循 ANSI C,并且完全与磁盘 I/O 层分开。它 可以被编译进内存较小的微控制器上,尽 管 RAM 大小小于扇区的大小。  非常小的内存消耗(44 个字节工作区+ 某些栈)。  支持 FAT32。  仅支持单一的分区和单个文件。  文件写函数受到一些限制。 打开这里,就可以看到很多 FatFs 的功能已经没有了 elm-chan.org/fsw/ff/00index_p.html 山外论坛 http://vcan123.com 山外メ雲ジ ~ 220 ~ 接口 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 FatFs 文件系统的函数 API 接口 函数名 作用 学习文件系统,就是学习调 用这些函数接口而已! f_mount 注册/注销一个工作区(文件系统对象) f_open 打开/创建文件 f_close 关闭文件 f_read 读取文件 f_write 写入文件 f_lseek 移动读写指针,也可以被用来扩展文件大小(簇预分配) f_truncate 截断文件大小 f_sync 冲洗一个写文件的缓存信息 f_opendir 打开一个目录 f_readdir 读取一个目录项 f_getfree 获取空闲簇的数目 f_stat 获取文件状态 f_mkdir 创建一个目录 f_unlink 移除一个对象 f_chmod 修改属性(读写、系统等) f_utime 修改时间戳(创建时间、修改时间、访问时间) f_rename 重命名 f_chdir 改变一个驱动器的当前目录 f_chdrive 改变当前驱动器 f_getcwd 检索当前目录 f_forward 读取文件数据并将其转发到数据流设备。 f_mkfs 在驱动器上创建一个文件系统 f_fdisk 分区 f_gets 从文件中读取一个字符串 f_putc 向文件中写入一个字符 f_puts 向文件中写入一个字符串 山外论坛 http://vcan123.com 山外メ雲ジ ~ 221 ~ f_printf f_tell f_eof f_size f_error 攻城略地之一天攻破 K60/KL26 —— 松飞科技 向文件中写入一个格式化字符串 获取当前读写指针 检查文件文件结束符 返回文件大小 测试文件错误 山外 FastFs 文件系统的磁盘 I/O 接口 由于 FatFs 模块完全与磁盘 I/O 层分开,因此底层磁盘 I/O 需要下列函数去读/写 物理磁盘以及获取当前时间。由于底层磁盘 I/O 模块并不是 FatFs 的一部分,因此它 必须由用户提供。 函数名 disk_initialize disk_status disk_read disk_write disk_ioctl get_fattime 作用 初始化磁盘驱动器 获取当前磁盘的状态 从磁盘驱动器中读取扇区 向磁盘驱动器中写入扇区 控制设备特定的功能以及磁盘读写以外的其它功能 获取当前时间 文件函数代码的返回值列表 返回值 FR_OK (0) FR_DISK_ERR FR_INT_ERR FR_NOT_READY FR_NO_FILE FR_NO_PATH FR_INVALID_NAME FR_DENIED FR_EXIST FR_INVALID_OBJECT FR_WRITE_PROTECTED 说明 函数成功 一个不可恢复的错误发生在底层(磁盘 I / O 函数)。 断言失败,发现一个错乱的内部过程。下列怀疑的可能性是:  FAT 结构变量有错误。  工作区(文件系统对象,文件对象…)被破坏,可能是栈溢出或任何其他 应用程序出错。这是最大的可能性错误。  在文件对象里发生了一个 FR_DISK_ERR 错误。 磁盘驱动器不能工作或磁盘初始化失败了。 找不到文件 找不到目录 传递的字符串是无效的文件路径。 访问请求被拒绝,原因如下:  写模式打开只读文件。  删除只读属性的文件或目录。  删除非空目录或当前目录  读取一个没有 FA_READ 标记的打开文件  修改一个没有 FA_WRITE 标志的打开文件  由于目录表已满,无法创建文件或目录  由于磁盘已满,无法创建目录 已经存在同名的文件或目录。 指定的文件或目录的对象结构是无效 驱动器设为写保护。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 222 ~ FR_INVALID_DRIVE FR_NOT_ENABLED FR_NO_FILESYSTEM FR_MKFS_ABORTED FR_TIMEOUT FR_LOCKED FR_NOT_ENOUGH_CORE FR_TOO_MANY_OPEN_FILES FR_INVALID_PARAMETER 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 驱动器号无效。 逻辑驱动器没有通过 f_mount 函数来注册工作区。 磁盘上没有有效的 FAT 卷。 由于下列原因之一,而导致函数在开始格式化前终止: • 磁盘容量太小 • 该驱动器不允许的簇大小。这可能发生在簇号快接近 0xFF7 和 0xFFF7。 函数被取消是因为线程安全控制超时。 (相关的选择: _TIMEOUT) 文件使用被拒绝是因为文件共享控制。(相关的选择: _FS_SHARE) 没有足够的内存操作。有以下原因: • 无法分配内存给长文件名工作缓冲区 (相关的选择:_USE_LFN) • 提供的表的大小不满足请求需要的大小。 打开文件的数量已经达到了最大值,并且没有更多的文件可以被打开。(相关的 选择:_FS_SHARE) 给定的参数是无效的 或 不一致的。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 223 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 资料推荐 山外 《轻松玩转 ARM Cortex-M4 微控制器-基于 Kinetis K60》 《轻松玩转 ARM Cortex-M4 微控制器-基于 Kinetis K60》是一本适合初学者进 阶的书籍。本书针对初学者对 C 语言、时序图、Datasheet 不熟悉,重点讲解这方面 的内容。从我自身经验来看,基础知识的缺乏是影响学习进度的主要原因。只要基础 扎实,那么任意来一款单片机,Datasheet+官方例程,基本上可以快速上手,甚至 写成自己的库。 (注:书上注明技术支持论坛是野火初学论坛,但由于本书作者(山外メ雲ジ) 已经脱离野火,野火论坛不再做任何飞思卡尔产品的技术支持,相应的技术支持论坛 转到【山外论坛】:http://vcan123.com) 有图有真相(出于合同限制,不能提供 PDF 文档的哦): 强化 C 语言基础知识 山外论坛 http://vcan123.com 山外メ雲ジ ~ 224 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 寄存器的内存分布,结合代码来理解 一般教程都是直接列表,我们的精心设计,与代码结合起来,方便大家理解底层的实现。 山外 保持 Datasheet 原味风格,便于学好看 Datasheet 单片机驱动开发,离开了 Datasheet,就没法写程序。学会看 Datasheet 是初学者入门的关键。本书寄存器描 述,保持 Datasheet 的原味风格,注释常见注意事项,以便读者能够看懂 Datasheet。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 225 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 软件分层 庞大的代码工程,离不开合理的软件分层,否则就没法维护。一般教程仅仅介绍函数接口,然后叫你调用就 完了,我们会对不同的模块进行分层,以便有立体效果方便理解。 图文并茂讲解 PORT 模块 很多人没法理解 PORT 模块,其实就是一个开关,控制复用到不同的功能。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 226 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 图文并茂讲解时序分析 山外论坛 http://vcan123.com 山外メ雲ジ ~ 227 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 结合实际测得的时序来进行分析 山外 山外资料下载方法 山外论坛:www.vcan123.com 山外论坛,是一个由专人管理资料帖子的论坛,有专门的资料专辑来收集分类不 同的资料。 大家可以在导航栏找到资料专辑,进去里面,可以看到有各种分类的帖子: 山外论坛 http://vcan123.com 山外メ雲ジ ~ 228 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 最常用的几个资料专辑 ,可以在导航栏里看到,看上图。 一般山外产品的资料,都可以在【山外资料下载】里找到。我们的 K60/KL26 库 都在这里可以找到。 进入相应的资料专辑里,会看到各种相关的帖子。这些帖子都是经典的资料。 帖子比较多,我们可以用浏览器自带的搜索功能来搜索相关的帖子。一般按 ctrl + F 键就可以进行搜索。图中是搜索“原理图”就可以容易找到相关的资料。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 229 ~ 攻城略地之一天攻破 K60/KL26 —— 松飞科技 山外 有的专辑,可能收集了比较多的帖子,那么有可能分开多页来显示,需要手动跳 到下一页继续搜索。 山外论坛 http://vcan123.com 山外メ雲ジ ~ 230 ~

    Top_arrow
    回到顶部
    EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。