Eclipse和单片机开发环境Keil的集成编译工具KeilMake 使用详细教程
Eclipse和单片机开发环境Keil的集成编译工具KeilMake
通过KeilMake可以在Eclipse上进行单片机开发且编译
使用方法:
配置Eclipse中项目Builder Settings的编译命令为
D:MyWorkSCMKeilMakeReleaseKeilMake D:MyWorkDevToolscommandkeilAT89S52.Uv2 ${ProjName} ${ProjDirPath} C:KeilUV2uv2.exe
红色部份修改为程序相应的路径即可,在Eclipse中写完程序后按Ctrl+B即可编译
AT89S52.Uv2 是Keil项目的模板文件,可根据个人需要进行修改,自带的模板配置是
AT89S52芯片,晶振为24Mhz,自动生成Hex文件
近期本人使用Keil进行单片机程序开发,感觉超不爽,在微软长期毒害下,我相信大部分跟着VS做开发的人们都对微软IDE的智能过于依赖。所以最 终我选择了Eclipse,虽然和微软的IDE相比还是有很大差距,不过对于我要做单片机开发的工作应该也可以满足了。在网上并没有找到Eclipse和 Keil可以很好集成在一起的插件,无而之下只好自己动手,搞搞科研了。
KeilMake就是这次的科研成果了,现与大家共同分享。
如果你的芯片和我现在用的一样是AT89S52,24Mhz,那么直接按照上面的方法即可使用,下面的内容可以略过。
如果是其它芯片,又和我一样想用Eclipse来进行单片机开发,那么请跟着我一步一步的操作。
环境安装,安装好Keil和Eclipse下面是我安装两个软件的版本
最新片Eclipse可以到这个地址 www.eclipse.org/downloads/ 下载C/C++版本的即可。
两个软件都安装好后我们就可以开始建立Keil模板了。
首先我们要知道自己的芯片的型号等信息,这里以举个AT89S52的例子,首先在Keil创建一个新项目
我们把项目文件保存为 stc89c52.Uv2 等会需要用到这个文件
CPU类型选择Atmel下面的AT89S52
新增加一个TEST.C的文件,内容空白就行了,因为我们不是在写代码,我们只是创建模板。
点击上面框框中的按钮对项目进行设置。
输入晶振的频率,我这块芯片是24MHz的。
设置生成时创建16进制文件,格式选择HEX-80
接下来用记事本打开刚才我们创建的项目文件 stc89c52.Uv2 留意红色框框的地方,这两个地方是我们需要修改的
File 1,1,<.test.c><test.c> 0x0 修改为 {CodeList}
stc89c52 修改为 {FileName}
完成上面步骤后,模板就创建完成了,我们把 stc89c52.Uv2 复制到别的地方,我放到D:MyWorkDevToolscommandkeil下
我把KeilMake.exe也放到这里了,这个不是必要的,我只是为了方便而已。
好了,现在到Eclipse了,如果在Eclipse上创建Keil项目呢?这里需要更正一个问题,我们不需要在Eclipse上创建Keil项 目,只要创建C项目就可以了,因为 KeilMake.exe 的工作就是把Eclipse上创建的C项目拿到Keil下去编译生成。
创建一个C项目
项目名称:testKeilProject
这里项目类型我选择Empty Project是因为我不需要用到MinGW GCC的类库,所以我选择空项目,然后Toolchains选择Other Toolchain,这一步我们可以直接点击Finish了。
现在到了最关键的设置时刻了,打开项目属性设置窗口。
Build command的值:D:MyWorkDevToolscommandkeilKeilMake.exe D:MyWorkDevToolscommandkeilstc89c52.Uv2 ${ProjName} ${ProjDirPath} C:KeilUV2uv2.exe
下面我们解释一下这行命令的意思
D:MyWorkDevToolscommandkeilKeilMake.exe KeilMake的路径。
D:MyWorkDevToolscommandkeilstc89c52.Uv2 我们刚才创建的Keil模板,以后只要是使用相同芯片做开发就可以使用这个模板。
${ProjName} 是Eclipse中的生成变量,是Eclipse里的项目名称,生成的HEX就是以这个值来命名。
${ProjDirPath} 是Eclipse中的项目目录
C:KeilUV2uv2.exe 是Keil的主程序路径
还有这里红色框框要留空,原值是all
点击File system按钮选择Keil目录下的c51inc目录
完成这一步后,基本上都完成了,我们来写一个程序测试一下。
Eclipse的自动完成功能
很简单的代码,来测试一下编译功能CTRL+B,记得要先保存再按CTRL+B,我刚开始时经常范这个错,所以代码没有编译到。
红色框框是编译生成输出的信息,看到
0 Error
和
creating hex file from "stc89c52"
这两行就是程序已经编译完成,在项目目录下会看到 stc89c52.hex 文件,把这个文件刷到单片机上试试看?
KeilMake是我学C语言的第一个程序,可能会存在一些BUG,如果使用过程中遇到什么问题,请随时和我联系,可发邮件到我的邮 箱:nick#workao.org 把#换为@
完。
附 KeilMake 下载地址:
STM32 入门教程 基于 MDK 的 SWD 两线串行仿真
(1) SWD 仿真模式概念简述
先所说 SWD 和传统的调试方式有什么不一样:
首先给大家介绍下经验之谈:
(一): SWD 模式比 JTAG 在高速模式下面更加可靠. 在大数据量的情况下面 JTAG 下载程序会 失败, 但是 SWD 发生的几率会小很多. 基本使用 JTAG 仿真模式的情况下是可以直接使用 SWD 模式的, 只要你的仿真器支 持. 所以推荐大家使用这个模式.
(二): 在大家 GPIO 刚好缺一个的时候, 可以使用 SWD 仿真, 这种模式支持更少的引脚.
(三): 在大家板子的体积有限的时候推荐使用 SWD 模式, 他需要的引脚少, 当然需要的 PCB 空间就小啦. 比如: 你可以选择一个很小的 2.54 间距的 5 芯端子做仿真接口.
(2) 仿真器对 SWD 模式支持情况
再说说市面上的常用仿真器对 SWD 仿真的支持情况.
(1) JLINKV6 支持 SWD 仿真模式. 速度较慢.
(2) JLINKV7 比较好的支持 SWD 仿真模式, 速度有了明显的提高. 速度是 JLINKV6 的 6 倍.
(3) JLINKV8 非常好的支持 SWD 仿真模式, 速度可以到 10M.
(4) ULINK1 不支持 SWD 模式
(5) 盗版 ULINK2 非常好的支持 SWD 模式. 速度可以达到 10M.
(6) 正版 ULINK2 非常好的支持 SWD 模式. 速度可以达到 10M.
再所说硬件上的不同:
(1) JLINKV6 需要的硬件接口为: GND, RST, SWDIO, SWDCLK
(2) JLINKV7 需要的硬件接口为: GND, RST, SWDIO, SWDCLK
(3) JLINKV8 需要的硬件接口为: VCC, GND, RST, SWDIO, SWDCLK
(4) ULINK1 不支持 SWD 模式
(5) 盗版 ULINK2 需要的硬件接口为: GND, RST, SWDIO, SWDCLK
(6) 正版 ULINK2 需要的硬件接口为: GND, RST, SWDIO, SWDCLK
由此可以看到只有 JLINKV8 需要 5 个引脚. 那么给大家介绍下为什么有了 VCC 这个引脚时候有好处, 我的个人理解: 我认为有这个引脚是最合适的, 仿真器对目标板子的仿真需要用到 RST 引脚, 其实使用仿真器内部的 VCC 做这个功能其实并不是非常美妙. 因此 JLINKV8 选择了只和目标板共 GND, 但是不共 VCC. 因此我觉得这种模式最合理, 当然通常情况下仿真器和目标板共 GND 和 VCC 是没有错的.
下面两张演示就是我使用 JLINKV8 进行下载程序到 Mini-STM32 开发板上仿真的图片. 程序代码中使用 了 uCGUI , 一共程序代码 120K 左右. 使用 JLINKV8 下载到芯片中只需要 5 秒左右.
(3) 在 MDK 中SWD 模式设置
接下来告诉大家怎么使用 SWD 设置:
打开工程 Option 设置:
在设置中按照上图设置成 SWD 模式, 速度你可以按照你的实际需求来设置, 如果你的板子供电系统不是特别稳定, 纹波比较大或者仿真线比较长可以设置成 500K 或者 1M , 如果环境很好当然可以选择 10M , 当然速度会飞起来.
记得不要忽略了左下方的那个
USB
还是 TCP 模式, 当然我们是 USB 模式, 因为有的时候默认是 TCP 模式, 这个时候我们忽略这个设置后会仿真常常连接不上的.
注意: 上面这个界面是在仿真器插在电脑上才会出现的界面, 就是说没有实际插入仿真器的话不会出现这个界面的.
当然虽然上面是 JLINKV8, ULINK2 下的设置也是一样的.
STM32 入门教程 RTC 实时时钟
(一) STM32 RTC 实时时钟概要
STM32内部RTC功能非常实用,它的供电和时钟是独立于内核的,可以说是STM32内部独立的外设模块,有加上RTC内部寄存器不受系统复位掉电的影 响,我们可以才用外部电池供电和32768表振晶体来实现真正RTC(实时时钟)功能。
这里引用手册里一段概述:
RTC由两个主要部分组成。第一部分(APB1接口)用来和 APB1总线相连。此单元还包含一组 16位寄存器,可通过 APB1总线对其进行读写操作。APB1接口以 APB1总线时钟为时钟,用来与 APB1总线接口。
另一部分(RTC核)由一系列可编程计数器组成,分成两个主要模块。第一个模块是 RTC的预分频模块,它可编程产生最长为 1秒的 RTC时间基准 TR_CLK。RTC的预分频模块包含了一个 20位的可编程分频器(RTC预分频器)。在每个TR_CLK周期中,如果在 RTC_CR 寄存器中设置了相应允许位,则 RTC产生一个中断(秒中断)。第 2个模块是一个 32位的可编程的计数器,它可被初始化为当前的系统时间。系统时间以 TR_CLK速度增长并与存储在 RTC_ALR寄存器中的可编程的时间相比较,如果 RTC_CR控制寄存器中设置了相应允许位,则比较匹配时将产生一个闹钟中断。
(二) 程序编写
(1) 系统启动后检查RTC是否已设置。由于RTC在BKP区域,当Vdd掉电之后可由后备电源提供电源,当后备电源连接到针脚VBAT上时,RTC的设置不会 由于外部电源的断开而丢失。在本例中写一个值到BKP_DR1中以标示RTC是否已配置,在启动之后程序检查BKP_DR1的值。
(2) 若BKP_DR1的值不正确:(BKP_DR1的值有误或者由于是第一次运行值还未写进去),则需要配置时间并且询问用户调整时间。
(3) 若BKP_DR1的值正确,则意味着RTC已配置,此时将在超级终端上显示时间。
(4) 整个工程包含3个源文件:STM32F10x.s、stm32f10x_it.c和main.c,其中STM32F10x.s为启动
代码
,所有中断服务子程序均在stm32f10x_it.c中,其它函数则在main.c中。下面分别介绍相关的函数,具体程序清单见参考程序。
函数RTC_IRQHandler用于处理秒中断事件,在每次遇到23:59:59时将时钟回零。
函数RTC_Configuration用于配置RTC模块。
函数USART_Scanf用于从PC超级终端中获取数字值,Time_Regulate利用函数USART_Scanf从超级终端获取新的RTC 时间 值,函数Time_Adjust则利用函数USART_Scanf设置新的RTC时间。函数Time_Display和Time_Show用于将RTC时 间转换了字符串送往USART1。
源文件其他函数,例如
GPIO
、RCC、NVIC、USART的配置,在此不作冗述。
(三) 仿真调试
(1) 使用Keil uVision3 通过ULINK 2
仿真器
连接实验板,打开实验例程目录Example10-RTC子目录下的RTC.Uv2例程,编译
链接
工程;
(2) 使用STM32开发板附带的串口线,连接开发板上的COM0和PC机的串口;
(3) 在PC机上运行Windows自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;
(4) 选择硬件调试模式,点击
MDK
的Debug菜单,选择Start/Stop Debug Session项或Ctrl+F5键,远程连接目标板并下载调试代码到目标系统中;
(5) 例程正常运行之后会在超级终端显示以下信息:
麦思网原创
MINI-STM32 STM32F103RBT6 RTC
RTC not yet configured....
RTC configured....
============TimeSettings===================
Please Set Hours:
在PC机上依次输入时钟、分钟、秒钟之后每隔1秒在超级终端上显示一次时间:
Please Set Hours: 12
Please Set Minutes: 0
Please Set Seconds: 0
Time: 12:00:00
(6) 程序正常运行并在开发外部电源保持的情况下,按下Reset
按钮
,PC超级终端上将继续显示正常时间:
External Reset occurred....
No need to configure RTC....
Time: 12:03:09
(7) 程序正常运行时断开开发板外部电源,然后重新接上外部电源,PC超级终端上也将继续显示正常时间:
Power On Reset occurred....
No need to configure RTC....
Time: 12:05:57
(8) 取下处理器板上的纽扣电池,并断开外部电源,然后重新接上外部电源,PC超级终端上将无法继续正常显示时间,PC超级终端将出现第(5)步所出现内容。
(9) 也可采用
软件
调试模式,利用USART窗口来
模拟
实现COM0的输入和输出。
请参考下图: 你需要在超级终端中敲击按键来设置时钟.
STM32 入门教程 内部温度传感器
(一) STM32 内部温度传感器概要
STM32 芯片内部一项独特的功能就是内部集成了一个温度传感器, 因为是内置, 所以测试的是芯片内部的温度, 如果芯片外接负载一定的情况下, 那么芯片的发热也基本稳定, 相对于外界的温度而言, 这个偏差值也是基本稳定的. 也就是说用 STM32 内部传感器来测量外界环境的温度.
在一些恶劣的应用环境下面, 可以通过检测芯片内部而感知设备的工作环境温度, 如果温度过高或者过低了 则马上睡眠或者停止运转. 可以保证您的设备工作的可靠性.
1. STM32内部温度传感器与ADC的通道16相连,与ADC配合使用实现温度测量;
2. 测量范围–40~125℃,精度±1.5℃。
3. 温度传感器产生一个随温度线性变化的电压,转换范围在2V < VDDA < 3.6V之间。
转换公式如下图所示:
手册中对于公式中的参数说明:
(二) 程序编写
写
代码
的时候, 在测量要求不怎么高的情况下, 公式可以简化.
简化的公式:
Temperature= (1.42 - ADC_Value*3.3/4096)*1000/4.35 + 25;
程序编写:
1. 初始化ADC , 初始化
DMA
可以参考贴子:
[原创] MINI-STM32 开发板
入门教程
(六) 基于 DMA 的 ADC
http://www.mystm32.com/bbs/viewthread.php?tid=42&extra=page%3D1
主意: 内部温度传感器是使用了 ADC1 的第 16 通道哦.
2. ADC_TempSensorVrefintCmd(ENABLE);
使能温度传感器和内部参考电压通道
3. 按照刚才列出的公式计算
Temperature= (1.42 - ADC_Value*3.3/4096)*1000/4.35 + 25;
第二步是做什么的呢? 参考下图:
(三) 仿真调试
(1) 使用Keil uVision3 通过ULINK 2
仿真器
连接实验板,使用MINI-STM32 开发板附带的串口线,连接实验板上的 UART1 和 PC 机的串口,打开实验例程目录下的ADC.Uv2例程,编译
链接
工程;
(2) 在 PC 机上运行 windows 自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;
(3) 点击
MDK
的Debug菜单,点击Start/Stop Debug Session;
(4) 全速运行程序, 显示结果如下所示。
麦思网原创教程
www.mystm32.com/bbs
STM32 入门教程 Unique Device ID
每一片 STM32 芯片内部拥有一个独一无二的 Unique Device ID, 96 Bit.
这个 ID 号可以提供给开发者很多优越的功能, 例如:
1. 可以把 ID 做为用户最终产品的序列号,帮助用户进行产品的管理。
2. 在某些需要保证安全性的功能代码运行前,通过校验此 ID,保证最终产品的某些功能的安全性。
3. 用 ID 配合加解密算法,对芯片内部的代码进行加加密,以保证用户产品的安全性和不可复制性。
这项功能相信对那些不希望自己的产品不被别人抄袭来说肯定是个非常不错的。
下面我就先大家介绍如何读出这个 ID:
这个 ID 号是放在片内 Flash 中的固定的位置,直接读取出来就行了. 96 位的独特 ID 位于地址 0x1FFFF7E8 ~ 0x1FFFF7F4 的系统存储区, 由 ST 公司在工厂中写入 (用户不能修改) 用户可以以字节、半字、或字的方式单独读取其间的任一地址.
(一) 配置好串口, 用于调试演示, 这一部份前面的章节我们已经介绍过了.
可以参考贴子:
[原创] MINI-STM32 开发板入门教程 (六) 基于 DMA 的 ADC
http://www.mystm32.com/bbs/viewthread.php?tid=42&extra=page%3D1
当然也可以直接下载我们的例程去读这部分的程序.
(二) 读取 Unique Device ID
u32 Dev_Serial0, Dev_Serial1, Dev_Serial2;
Dev_Serial0 = *(vu32*)(0x1FFFF7E8);
Dev_Serial1 = *(vu32*)(0x1FFFF7EC);
Dev_Serial2 = *(vu32*)(0x1FFFF7F0);
(三) 仿真调试
(1) 使用Keil uVision3 通过ULINK 2仿真器连接实验板,使用MINI-STM32 开发板附带的串口线,连接实验板上的 UART1 和 PC 机的串口,打开实验例程目录下的ADC.Uv2例程,编译链接工程;
(2) 在 PC 机上运行 windows 自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;
(3) 点击MDK 的Debug菜单,点击Start/Stop Debug Session;
(4) 全速运行程序, 显示结果如下所示。
麦思网原创教程
www.mystm32.com/bbs
MINI-STM32 STM32F103RBT6 Unique Device ID:
066C0033 32353958 43195835
备注: 其中最后一行的数字就是我的 MINI-STM32 开发板中 STM32F103RBT6 的 Unique Device ID. 您的 ID 当然肯定会和我的不一样哦.
STM32 入门教程 工业现场总线 CAN
(一) 工业现场总线 CAN 的基本介绍以及 STM32 的 CAN 模块简介
首先通读手册中关于CAN的文档,必须精读。
STM32F10xxx 参考手册Rev7V3.pdf
http://www.mystm32.com/bbs/redirect.php?tid=255&goto=lastpost#lastpost
需要精读的部分为 RCC 和 CAN 两个章节。
为什么需要精读 RCC 呢?因为我们将学习 CAN 的波特率的设置,将要使用到 RCC 部分的设置,因此推荐大家先复习下这部分中的几个时钟。
关于 STM32 的 can 总线简单介绍
bxCAN 是基本扩展 CAN (Basic Extended CAN) 的缩写,它支持 CAN 协议 2.0A 和 2.0B 。它的设计目标是,以最小的 CPU 负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。
对于安全紧要的应用,bxCAN 提供所有支持时间触发通信模式所需的硬件功能。
主要特点
· 支持 CAN 协议 2.0A 和 2.0B 主动模式
· 波特率最高可达 1 兆位 / 秒
· 支持时间触发通信功能
发送
· 3 个发送邮箱
· 发送报文的优先级特性可软件配置
· 记录发送 SOF 时刻的时间戳
接收
· 3 级深度的2个接收 FIFO
· 14 个位宽可变的过滤器组 - 由整个 CAN 共享
· 标识符列表
· FIFO 溢出处理方式可配置
· 记录接收 SOF 时刻的时间戳
可支持时间触发通信模式
· 禁止自动重传模式
· 16 位自由运行定时器
· 定时器分辨率可配置
· 可在最后 2 个数据字节发送时间戳
管理
· 中断可屏蔽
· 邮箱占用单独 1 块地址空间,便于提高软件效率
(二) STM32 CAN 模块工作模式
STM32 的 can 的工作模式分为:
/* CAN operating mode */
#define CAN_Mode_Normal ((u8)0x00) /* normal mode */
#define CAN_Mode_LoopBack ((u8)0x01) /* loopback mode */
#define CAN_Mode_Silent ((u8)0x02) /* silent mode */
#define CAN_Mode_Silent_LoopBack ((u8)0x03) /* loopback combined with silent mode */
在此章我们的 Mini-STM32 教程中我们将使用到 CAN_Mode_LoopBack 和 CAN_Mode_Normal 两种模式。
我们第一步做的就是使用运行在 CAN_Mode_LoopBack 下进行自测试。
在参考手册中 CAN_Mode_LoopBack (环回模式) 的定义如下:
环回模式可用于自测试。为了避免外部的影响,在环回模式下 CAN 内核忽略确认错误 (在数据 / 远程帧的确认位时刻,不检测是否有显性位) 。在环回模式下,bxCAN 在内部把 Tx 输出回馈到 Rx 输入上,而完全忽略 CANRX 引脚的实际状态。发送的报文可以在 CANTX 引脚上检测到。
因此这种模式也特别适合大家做好硬件后自测程序。
(三) CAN 接口端口映射
STM32 中的 CAN 物理引脚脚位可以设置成三种:
默认模式,重定义地址1模式,重定义地址2模式
。
在我们的 Mini-STM32 上面没有接出 CAN 的接口芯片, 所以我们可以利用
RealView MDK
的 CAN 软件
模拟
模块来做实验.
-------------------------------------------------------------------------
默认模式
/* Configure CAN pin: RX */
GPIO
_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure CAN pin: TX */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
------------------------------------------------------------------------
重定义地址1模式
/* Configure CAN pin: RX */
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
//GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Configure CAN pin: TX */
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Configure CAN Remap 重影射 */
//GPIO_PinRemapConfig(GPIO_Remap1_CAN, ENABLE);
-------------------------------------------------------------------------
重定义地址2模式
/* Configure CAN pin: RX */
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
//GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Configure CAN pin: TX */
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Configure CAN Remap 重影射 */
//GPIO_PinRemapConfig(GPIO_Remap2_CAN, ENABLE);
-------------------------------------------------------------------------
设置完 CAN 的引脚之后还需要打开 CAN 的时钟:
/* CAN Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN, ENABLE);
(四) CAN 波特率设置
4、我们需要搞明白CAN波特率的设置,这个章节也是使用CAN的最重要的部分之一,因为这实际应用中我们需要根据我们实际的场合来选择 CAN 的波特率。
一般情况下面1M bps 的速率下可以最高可靠传输 40 米以内的距离。
在 50K 以下的波特率中一般可以可靠传输数公里远。
对于波特率的设置需要详细学习参考手册对应部分的解释。我们在调试软件的时候可以使用示波器来测试 CANTX 引脚上的波形的波特率,这样可以得到事半功倍的效果,大大的缩短调试学习的时间。
// ***************************************************************
// BaudRate = 1 / NominalBitTime
// NominalBitTime = 1tq + tBS1 + tBS2
// tq = (BRP[9:0] + 1) x tPCLK
// tPCLK = CAN's clock = APB1's clock
// ****************************************************************
也就是BaudRate = APB1 / ((BS1 + BS2 + 1) * Prescaler)
这里注意的是采用点的位置,也就时BS1,BS2的设置问题,这里我也找了一些资料,抄录下来给大家,是 CANopen 协议中推荐的设置。
1Mbps 速率下,采用点的位置在6tq位置处,BS1=5, BS2=2
500kbps 速率下,采用点的位置在8tq位置处,BS1=7, BS2=3
250kbps 速率下,采用点的位置在14tq位置处,BS1=13, BS2=2
125k, 100k, 50k, 20k, 10k 的采用点位置与 250K 相同。
因此我们需要重视的有软件中的这么几个部分:
// 设置 AHB 时钟(HCLK)
// RCC_SYSCLK_Div1 AHB 时钟 = 系统时钟
RCC_HCLKConfig(RCC_SYSCLK_Div8);
// 设置低速 AHB 时钟(PCLK1)
// RCC_HCLK_Div2 APB1 时钟 = HCLK / 2
RCC_PCLK1Config(RCC_HCLK_Div2);
// PLLCLK = 8MHz * 8 = 64 MHz
// 设置 PLL 时钟源及倍频系数
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_8);
CAN 波特率设置中需要的就是PCLK1 的时钟。
CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1=CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;
CAN_InitStructure.CAN_Prescaler=5;
通过上面部分的时钟设置我们已经可以算出我们的波特率了
CAN_bps = PCLK1 / ((1 + 7 + 8) * 5) = 25K bps
大家也可以实际测试中修改时钟值来通过示波器测试我们需要的波特率是否正确例如将PLLCLK 设置降低一半:
// PLLCLK = 8MHz * 4 = 32 MHz
// 设置 PLL 时钟源及倍频系数
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_4);
那么我们得到的CAN_bps也会降低一半。
接下来还可以修改 HCLK 和 PCLK1 ,其实最终这几个分频和倍频值最终影响的都是 PCLK1。
通过几次试验,相信大家应该很容易掌握波特率的设置了。
设置完波特率我们直接测试函数:
/* CAN transmit at 100Kb/s and receive by polling in loopback mode*/
TestRx = CAN_Polling();
if (TestRx == FAILED)
{
/* Turn on led connected to PA.00 pin (LD1) */
GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
else
{
/* Turn off led connected to PA.00 pin (LD1) */
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
/* CAN transmit at 500Kb/s and receive by interrupt in loopback mode*/
TestRx = CAN_Interrupt();
if (TestRx == FAILED)
{
/* Turn on led connected to PA.01 pin (LD2) */
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
else
{
/* Turn off led connected to PA.01 pin (LD2) */
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
将
CAN 软件仿真模拟器
调用出来.
大家可以仿真程序,当程序中 Test 等于 Passed 那么说明 Loopback 模式测试通过了。
并且在 CAN 通讯框中我们可以看到发送和接收到的数据:
回循模式下的源代码, 基于 MDK3.5:
Example7.1-CAN LoopBack Mode.rar
(493.79 KB)
到此时说明如果大家只有一块CAN模块的时候学习可以告一个段落了,不过这个并不代表大家就已经掌握了 CAN 了,正真要掌握它,大家还是需要看大量的 CAN 部分的资料,参考手册部分的也是不够的,市面上有几本专门介绍现场总线和CAN总线的书,推荐大家买来经常翻翻看看,这样到需要实际应用的时候才可以做到 如鱼得水。
(五) 正常模式
完成了 loopback 模式的测试之后接下来我们需要学习的就是多机通讯了,当然由于我们的 Mini-STM32 没有将 CAN 接口引出来, 所以我们没有办法在板子上面做这部分的试验了,只能在 RealView MDK 的软件中进行模拟。
如果您拥有两块带 CAN 硬件的 STM32 的板子,您需要自己构建硬件的物理层的连接, 使用三根线将 CANH,CANL,GND 三根线直连,当然你要接好终端电阻才能保证通讯的正常通讯,当两块板子都跳好后我们使用万用表测量下 CANH和CANL之间的电阻是否为 60 欧姆。多块板子多机通讯的是否你只需要在总线的主机端和最后一端接上终端电阻就可以了.
在初始化完成后,软件应该让硬件进入正常模式,以便正常接收和发送报文。软件可以通过对 CAN_MCR 寄存器的INRQ位清 '0',来请求从初始化模式进入正常模式,然后要等待硬件对 CAN_MSR 寄存器的 INAK 位置 '1' 的确认。在跟 CAN 总线取得同步,即在 CANRX 引脚上监测到 11 个连续的隐性位 (等效于总线空闲) 后,bxCAN 才能正常接收和发送报文。
不需要在初始化模式下进行过滤器初值的设置,但必须在它处在非激活状态下完成 (相应的 FACT 位为 '0' ) 。而过滤器的位宽和模式的设置,则必须在初始化模式中进入正常模式前完成。
准备工作做完我们需要设置 CAN 通讯部份软件。
我们把 TestStatus CAN_Polling(void) 函数和 TestStatus CAN_Interrupt(void) 函数中的 LoopBack 模式修改为 Normal 模式.
//CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;
CAN_InitStructure.CAN_Mode=CAN_Mode_Normal;
接下来我们就可以做实验了. 但是由于 RealView MDK 的 CAN 模块没有办法接收, 所以我们只做发送的例子.
我们的例子中分别发送两帧数据:
(1) TestStatus CAN_Polling(void) 查询发送
第一帧数据为: ID 为 0x11, 数据为 8 个字节的一个数据包.
TxMessage.StdId=0x11;
TxMessage.RTR=CAN_RTR_DATA;
TxMessage.IDE=CAN_ID_STD;
TxMessage.DLC=8;
TxMessage.Data[0]=0x01;
TxMessage.Data[1]=0x02;
TxMessage.Data[2]=0x03;
TxMessage.Data[3]=0x04;
TxMessage.Data[4]=0x05;
TxMessage.Data[5]=0x06;
TxMessage.Data[6]=0x07;
TxMessage.Data[7]=0x08;
(2) TestStatus CAN_Interrupt(void) 中断发送
第二帧数据为:ID 为 0x1234, 数据为 8 个字节的一个数据包.
TxMessage.StdId=0x12;
TxMessage.ExtId=0x34;
TxMessage.IDE=CAN_ID_EXT;
TxMessage.RTR=CAN_RTR_DATA;
TxMessage.DLC=8;
TxMessage.Data[0]=0x11;
TxMessage.Data[1]=0x22;
TxMessage.Data[2]=0x33;
TxMessage.Data[3]=0x44;
TxMessage.Data[4]=0x55;
TxMessage.Data[5]=0x66;
TxMessage.Data[6]=0x77;
TxMessage.Data[7]=0x88;
CAN_Transmit(&TxMessage);
在主函数中初始化之后加上这两句发送函数:
/* CAN transmit at 100Kb/s and receive by polling in Normal mode*/
CAN_Polling();
while(i++ < 1000);
/* CAN transmit at 500Kb/s and receive by interrupt in Normal mode*/
CAN_Interrupt();
程序改完了, 我们需要编译通过后, 点软件仿真.
将
CAN 软件仿真模拟器
调用出来.
接下来我们全速运行到 while(1) 就可以看到结果了.
STM32 入门教程 基于 DMA 的 ADC
(一) STM32 ADC 模块介绍
下载 (52.26 KB)
下载 (69.24 KB)
(二) 程序编写
(1) 设置 ADC 的地址
#define ADC1_DR_Address ((u32)0x4001244C)
(2) 初始化 DMA 和 ADC 模块与应用程序
/* DMA channel1 configuration ----------------------------------------------*/
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; // 外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue; // 内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // DMA 传输方向单向
DMA_InitStructure.DMA_BufferSize = 1; // 设置DMA在传输时缓冲区的长度 word
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置DMA外设递增模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; // 设置DMA内存递增模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据字长
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据字长
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 设置传输模式连续不断的循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 设置DMA的优先级别
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 设置DMA的2个memory中的变量互相访问
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
/* ADC1 configuration ------------------------------------------------------*/
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立工作模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描方式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 外部触发禁止
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 用于转换的通道数
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channel14 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_55Cycles5);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibaration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibaration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(1)
{
AD_value = ADC_GetConversionValue(ADC1);
delay();
}
(三) 仿真调试
(1) 使用Keil uVision3 通过ULINK 2
仿真器
连接实验板,使用MINI-STM32 开发板附带的串口线,连接实验板上的 UART1 和 PC 机的串口,打开实验例程目录下的ADC.Uv2例程,编译
链接
工程;
(2) 在 PC 机上运行 windows 自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;
(3) 点击
MDK
的Debug菜单,点击Start/Stop Debug Session;
(4) 旋转电位器 R19,可以看到串口输出数值不断变化,正常显示结果如下所示。
usart1 print AD_value --------------------------
The current AD value = 0x0425
The current AD value = 0x0423
The current AD value = 0x0421
The current AD value = 0x0422
The current AD value = 0x0420
The current AD value = 0x0416
The current AD value = 0x03B6
The current AD value = 0x0841
The current AD value = 0x08C3
The current AD value = 0x08C0
The current AD value = 0x08BE
The current AD value = 0x09E9
The current AD value = 0x0A12
The current AD value = 0x0ACA
The current AD value = 0x0B0D
The current AD value = 0x0B10
The current AD value = 0x0B0E
....
....
(5) 若无开发板,读者也可以使用
软件
仿真模式来完成程序运行。
下(77.9 KB)
STM32 入门教程 异步串口双工通讯
(一) STM32 的 USART 模拟介绍
通用同步异步收发器(USART)提供了一种灵活的方法来与使用工业标准NR 异步串行数据格式的外部设备之间进行全双工数据交换。 USART利用分数波特率发生器提供宽范围的波特率选择。
它支持同步单向通信和半双工单线通信。它也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。用于多缓冲器配置的DMA方式,可以实现高速数据通信。
主要特性:
全双工的,异步通信
NR 标准格式
分数波特率发生器系统
-发送和接收共用的可编程波特率,最高到4.5Mbits/s
可编程数据字长度(8位或9位)
可配置的停止位 -支持1或2个停止位
LIN主发送同步断开符的能力以及LIN从检测断开符的能力
- 当USART硬件配置成LIN时,生成13位断开符;检测10/11位断开符
发送方为同步传输提供时钟
IRDA SIR 编码器解码器
- 在正常模式下支持3/16位的持续时间
智能卡模拟功能
- 智能卡接口支持ISO7816 -3标准里定义的异步协议智能卡
- 智能卡用到的0.5和1.5个停止位
单线半双工通信
使用DMA的可配置的多缓冲器通信
- 在保留的SRAM里利用集中式DMA缓冲接收/发送字节
单独的发送器和接收器使能位
检测标志
- 接收缓冲器满
- 发送缓冲器空
- 传输结束标志
校验控制
- 发送校验位
- 对接收数据进行校验
四个错误检测标志
- 溢出错误
- 噪音错误
- 帧错误
- 校验错误
10个带标志的中断源
- CTS改变
- LIN断开符检测
- 发送数据寄存器
- 发送完成
- 接收数据寄存器
- 检测到总线为空
- 溢出错误
- 帧错误
- 噪音错误
- 校验错误
多处理器通信 - - 如果地址不匹配,则进入静默模式
从静默模式中唤醒(通过空闲总线检测或地址标志检测)
两种唤醒接收器的方式
- 地址位(MSB)
- 空闲总线
(二) 程序编写
(1) 在 RCC_Configuration 函数中, 打开串口时钟
/* Enable USART1, GPIOA, GPIOx and AFIO clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOx
| RCC_APB2Periph_AFIO, ENABLE);
(2) 设置串口的 RTX, TDX IO 口的属性
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART1 Rx (PA.10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Tx (PA.09) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
(3) 移植 fputc 函数
int fputc(int ch, FILE *f)
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART1, (u8) ch);
/* Loop until the end of transmission */
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
{
}
return ch;
}
(4) 主函数中初始化串口设置
USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
/* Configure the USART1 synchronous paramters */
USART_ClockInit(USART1, &USART_ClockInitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART1 basic and asynchronous paramters */
USART_Init(USART1, &USART_InitStructure);
/* Enable USART1 */
USART_Cmd(USART1, ENABLE);
(5) 编写应用程序
printf("rn 欢迎来到麦思网论坛 rn");
printf("rn http://www.mystm32.com rn");
printf("rn Please Input Character From Keyboard rn");
while(1 )
{
if(USART_GetFlagStatus(USART1,USART_IT_RXNE)==SET)
{
i = USART_ReceiveData(USART1);
printf(" %c",i&0xFF); /* print the input char */
}
}
(三) 调试仿真程序
(1) 使用Keil uVision3 通过ULINK 2仿真器连接实验板,打开实验例程目录USART_TEST子目录下的USART.Uv2例程,编译链接工程;
(2) 使用MINI-STM32开发板附带的串口线,连接开发板上的COM和PC机的串口;
(3) 在PC机上运行windows自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;
(4) 选择硬件调试模式,点击MDK 的Debug菜单,选择Start/Stop Debug Session项或Ctrl+F5键,远程连接目标板并下载调试代码到目标系统中;
(5) 例程正常运行之后会在超级终端显示以下信息:
" 欢迎来到麦思网论坛"
"http://www.mystm32.com"
"Please Input Character From Keyboard"
在PC机的键盘上输入的字符,将在超级终端上显示。
(6) 也可选择
软件
调试模式,点击MDK 的Debug菜单,打开串行窗口,选择Start/Stop Debug Session
项或Ctrl+F5键,在串行窗口中也可看到与第(5)步超级终端中所显示的相同内容。
STM32 入门教程 GPIO 简单应用和外部中断
这个章节我们将学习最基本的 STM32 的 GPIO 的应用. 我们将分为两个章节来学习.
第一部份: GPIO 的基本应用和 IO 口的配置
第二部份: 外部中断的使用
--------------------------------------------------------------------------
1: 设计要求:
开发板上有 2 个 LED, 我们的目的为有规律的点亮 LED1 和 LED2. 当按键按下去的时候所有的灯灭, 等待 2 秒钟后恢复有规律的点亮.
2: 硬件电路:
3: 软件程序设计:
(1) 根据要求配置 GPIOA 中的 PA0,PA1 为输出, PA3, PA8 为输入
对于下面程序中的 GPIO_InitStructure.GPIO_Speed 和 GPIO_InitStructure.GPIO_Mode 推荐大家看下面两篇文章.
备注: 当STM32的GPIO端口设置为输出模式时,有三种速度可以选择:2MHz、10MHz和50MHz,这个速度是指I/O口驱动电路的速度,是用来选择 不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。
备注: 共有8种模式,可以通过编程选择:
1. 浮空输入
2. 带上拉输入
3. 带下拉输入
4.
模拟
输入
5. 开漏输出——(此模式可实现hotpower说的真双向IO)
6. 推挽输出
7. 复用功能的推挽输出
8. 复用功能的开漏输出
模式7和模式8需根据具体的复用功能决定。
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure PA. as Output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure PA3,PA8 as input floating (EXTI Line3 , EXTI Line8) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
(2) 打开 GPIOA 的时钟
, 因为 STM32 是一个低功耗的 MCU , 每一个你使用的外围设备都需要单独开启时钟, 如果不开启将不能使用, 这个也是对于 STM32 初学者容易疏忽的地方
/* Enable GPIOA and AFIO clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_AFIO, ENABLE);
STM32共有5种时钟源,其中三种不同的时钟源可用作为驱动系统时钟(SYSCLK);
1、HSI 由内部8MHz RC振荡器产生,它是可以直接用来作为系统时钟或经2分频后作为PLLSRC输入。
HIS时钟频率在出厂时被校准在1%(25°C),在系统复位时,工厂校准值会被装载到时钟控制寄存器的HISCAL[7..0] 位。
用户可以通过更改HISCAL[4..0]来调整HSI频率。
另外时钟寄存器中有一个HSIRDY位用来指示HSI RC是不稳定工作,在时钟启过后,直到这个标志位置被硬件置1后,HSI RC时钟才被输出。
HSI RC时钟还可以用时钟寄存器中的HSION位来启动和关闭。
HSI时钟同时也是HSE晶体荡振器的备用时钟源。
使用HSE时钟,程序设置时钟参数流程:
1、将RCC寄存器重新设置为默认值 RCC_DeInit;
2、打开外部高速时钟晶振HSE RCC_HSEConfig(RCC_HSE_ON);
3、等待外部高速时钟晶振工作 HSEStartUpStatus = RCC_Wait
For
HSEStartUp();
4、设置AHB时钟 RCC_HCLKConfig;
5、设置高速AHB时钟 RCC_PCLK2Config;
6、设置低速速AHB时钟 RCC_PCLK1Config;
7、设置PLL RCC_PLLConfig;
8、打开PLL RCC_PLLCmd(ENABLE);
9、等待PLL工作 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
10、设置系统时钟 RCC_SYSCLKConfig;
11、判断是否PLL是系统时钟 while(RCC_GetSYSCLKSource() != 0x08)
12、打开要使用的外设时钟 RCC_APB2PeriphClockCmd()/RCC_APB1PeriphClockCmd()
具体设置请参考下面的文章
(3) 设置外部中断
, 所有的 GPIO 口都可以作为外部中断源. 具体可以参考下面这篇文章.
/* Connect EXTI Line3 to PA.3 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);
/* Configure EXTI Line3 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
同样不要忘记打开时钟, 我们在打开 PA 口的时候已经加上了 RCC_APB2Periph_AFIO, 这里再提醒大家一下.
/* Enable GPIOA and AFIO clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_AFIO, ENABLE);
最后就是编写外部中断入口函数.
void EXTI3_IRQHandler(void)
{
int i;
if(EXTI_GetITStatus(EXTI_Line9) != RESET)
{
GPIO_SetBits(GPIOA,GPIO_Pin_0);
GPIO_SetBits(GPIOA,GPIO_Pin_1);
for(i=0;i<=8000000;i++);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
for(i=0;i<=1000000;i++)
/* Clear the EXTI line 3 pending bit */
EXTI_ClearITPendingBit(EXTI_Line3);
}
}
不要忘记在中断函数处理完成后清掉标志位,不然会不停的进入中断.
(4) 编译与调试
我们已经完成所有程序编写部份, 接下来就是将工程编译成功后下载到我们的 Mini-STM32 开发板中进行调试和仿真.
如果看到 LED 有规律的点亮和熄灭, 按下
按钮
后 LED 先是一起熄灭, 在一起点亮, 然后恢复有规律的点亮这个过程, 说明我们已经达到我们的设计目标.
--------------------------------------------------------------------------
总结: 我们学习完了这篇教程之后, 相信大家对下面几个方面的内容已经掌握了.
* GPIO 的设置
* STM32 GPIO 的优势
* STM32 的时钟结构
* 外部中断的配置
* 外部中断和外部事件的区别
STM32 入门教程 系统时钟 SysTick
(一) 背景介绍
在传统的嵌入式系统软件按中通常实现 Delay(N) 函数的方法为:
for(i = 0; i <= x; i ++);
x --- 对应于 对应于 N 毫秒的循环值
对于STM32系 列微处理器来说,执行一条指令只有几十个 ns,进行 for 循环时,要实现 N 毫秒的 x 值非常大,而且由于系统频率的宽广,很难计算出延时 N 毫秒的精确值。针对 STM32 微处理器,需要重新设计一个新的方法去实现该功能,以实现在程序中使用 Delay(N)。
(二) STM32 SysTick 介绍
Cortex-M3 的内核中包含一个 SysTick 时钟。SysTick 为一个 24 位递减计数器,SysTick 设定初值并使能后,每经过 1 个系统时钟周期,计数值就减 1。计数到 0 时,SysTick 计数器自动重装初值并继续计数,同时内部的 COUNTFLAG 标志会置位,触发中断 (如果中断使能情况下)。
在 STM32 的应用中,使用 Cortex-M3 内核的 SysTick 作为定时时钟,设定每一毫秒产生一次中断,在中断处理函数里对 N 减一,在Delay(N) 函数中循环检测 N 是否为 0,不为 0 则进行循环等待;若为 0 则关闭 SysTick 时钟,退出函数。
注: 全局变量 TimingDelay , 必须定义为 volatile 类型 , 延迟时间将不随系统时钟频率改变。
(三) ST SysTick 库文件
使用ST的函数库使用systick的方法
1、调用SysTick_CounterCmd() -- 失能SysTick计数器
2、调用SysTick_ITConfig () -- 失能SysTick中断
3、调用SysTick_CLKSourceConfig() -- 设置SysTick时钟源。
4、调用SysTick_SetReload() -- 设置SysTick重装载值。
5、调用SysTick_ITConfig () -- 使能SysTick中断
6、调用SysTick_CounterCmd() -- 开启SysTick计数器
(四) SystemTick 工程实战
外部晶振为 8 MHz,9 倍频,系统时钟为 72MHz,SysTick 的最高频率为9MHz(最大为HCLK / 8),在这个条件下,把 SysTick 效验值设置成9000,将 SysTick 时钟设置为 9 MHz, 就能够产生 1ms 的时间基值,即 SysTick 产生 1ms 的中断。
/* Configure the system clocks */
RCC_Configuration();
SysTick_Configuration();
第一步: 配置 RCC 寄存器 和 SysTick 寄存器
RCC_Configuration: 配置 RCC 寄存器
void RCC_Configuration(void)
{
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* Flash 2 wait state */
FLASH_SetLatency(FLASH_Latency_2);
/* Enable Prefetch Buffer */
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
/* PLLCLK = 8MHz * 9 = 72 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while(RCC_GetSYSCLKSource() != 0x08)
{
}
}
/* Enable GPIOA and AFIO clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_AFIO, ENABLE);
}
SysTick_Configuration: 配置 SysTick
void SysTick_Configuration(void)
{
/* Select AHB clock(HCLK) as SysTick clock source */
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
/* Set SysTick Priority to 3 */
NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick, 3, 0);
/* SysTick interrupt each 1ms with HCLK equal to 72MHz */
SysTick_SetReload(72000);
/* Enable the SysTick Interrupt */
SysTick_ITConfig(ENABLE);
}
第二步: 配置 SysTick 中断函数
这里我们定义了一个 TestSig 全局变量, 用于我们使用 Keil 软件自带的逻辑分析仪来分析.
volatile vu32 TimingDelay = 0;
vu8 TestSig = 0;
void SysTickHandler(void)
{
TimingDelay--;
if(TimingDelay % 2)
{
TestSig = 1;
}
else
{
TestSig = 0;
}
}
第三步: 编写 Delay 延时函数
Delay: 系统延时函数, 使用系统时钟操作.
void Delay(u32 nTime)
{
/* Enable the SysTick Counter */
SysTick_CounterCmd(SysTick_Counter_Enable);
TimingDelay = nTime;
while(TimingDelay != 0);
/* Disable the SysTick Counter */
SysTick_CounterCmd(SysTick_Counter_Disable);
/* Clear the SysTick Counter */
SysTick_CounterCmd(SysTick_Counter_Clear);
}
第四步: 主函数中调用 Delay
在 Mini-STM32 开发板上有两个 LED 灯, 分别是 PA0, PA1. 我们做个流水灯程序, 让他们循环点亮.
while(1)
{
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay(100);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay(100);
GPIO_SetBits(GPIOA,GPIO_Pin_1);
Delay(100);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
Delay(100);
}
(五) 仿真调试
把工程便宜通过后, 进入软件仿真
如下图所示:点击工程快捷菜单的逻辑分析仪
在逻辑分析仪中我们点击 Setup 按键会弹出安装对话框.
点右上方的 "新建" 图标, 在菜单中输入 "TestSig" 这个全局变量.
添加完之后就可以点 Close 了. 如果您仿真完可以点击 左下方的 "Kill All" 删除所有监视变量.
全速运行后就可以看到下面的波形了哦
如果你使用仿真器在 Mini-STM32 上调试的话你还可以看到两个 LED 在跑跑马灯程序了.
到此我们这章节的教程就结束了, 相信大家也掌握了 System Tick 的用法了.