《微控制器STM32原理与应用》
实验指导书
河南工业大学
2019-07
目 录
第一章 微控制系统综合实训平台介绍 ................................................................... 1 第二章 KEIL MDK5认识 (实验 1) ....................................................................... 6 第三章 系统滴答定时器的应用 (实验 2) ............................................................. 28 第四章 跑马灯实验 (实验 3) ............................................................................... 32 第五章 按键输入实验 (实验 4) ............................................................................. 41 第六章 串口实验 (实验 5) ..................................................................................... 45 第七章 外部中断实验 (实验 6) ............................................................................. 50 第八章 定时器中断实验 (实验7) ........................................................................ 56 第九章 PWM输出实验 (实验 8).......................................................................... 62 第十章 输入捕获实验 (实验 9) ........................................................................... 68 第十一章 基于CAN总线的网络式超声波测距仪设计 (实验 10) .................. 73 第十二章 伺服电机控制器设计(实验 11) ........................................................... 91 第十三章 单相太阳能并网逆变器SPWM发生器设计 (实验 12) ................. 103 附录一:带有温度补偿的超声波测距电路原理图 ............................................... 111
第一章 微控制系统综合实训平台介绍
1 背景
微控制器是将微型计算机的主要部分集成在一个芯片上的单芯片微型计算机,其诞生于20世纪70年代中期,经过几十年的发展,其成本越来越低,而性能越来越强大,这使其应用已经无处不在,遍及各个领域。
微控制器更是自动化、测控技术与仪器等专业的主要专业课程,是学生就业的重要支撑课程,是工程认证、工程教育理念的主要支撑方向。 (1) 目前微控制器类课程的缺点 ➢ 缺少实验对象
缺少伺服电机、变频器、步进电机、丝杠、逆变器、温度变送器、超声波等外围元器件
➢ 微控制器型号落后,外设较少
STCC51、DSP等微控制器与STM32相比,搭载的外设很少,无法同时学习到各种外设的应用
➢ 实验内容单一,无综合性实验
实验内容集中于输入输出、定时器、中断等基础功能,缺少综合性实验内容 ➢ 人机交互功能有限,无LCD、OLED等 (2) 本系统的特点 ➢ 控制对象丰富
本实验系统包含伺服电机、直线滑台、三相交流电机、温度变送器、超声波、逆变器、TFTLCD等外围实验对象
➢ 微控制器型号为STM32F103ZET6基于C语言编程
基于MDK5编程,实验指导书C语言程序模块化架构
➢ 综合性实验丰富
本系统包含:计算器、基于CAN总线的网络式超声波测距仪设计、温度PID
曲线控制、简易伺服电机控制器设计、单相太阳能并网逆变器SPWM发生器设计等综合性实验内容
1
➢ 模块式架构
本实验系统的综合实验各模块,便于组合实验内容,节省场地资源
2 实验内容组织
序号 1 2 3 4 5 6 7 8 9 10 11 12 教学内容 开发软件安装、工程模板的建立 系统滴答定时器应用 跑马灯实验 按键输入实验 串口实验 外部中断实验 定时器中断实验 PWM输出实验 输入捕获实验 基于CAN总线的网络式超声波测距仪设计 简易伺服电机控制器设计 单相太阳能并网逆变器SPWM发生器设计 合计 建议学时 2 2 2 2 2 2 2 2 2 8 8 8 42 教学方法 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 讲授、讨论、实验 3 实训平台组成
(1) 实训平台本体
2
图1.1 PLC综合实训平台
综合实训平台本体包含MCU、继电器、串口、CAN、AD/DA、流水灯、矩阵键盘、数字温度传感器、数码管、TFTLCD、24V隔离输入,其它部分位于扩展单元上。
(2) 基于CAN总线的网络式超声波测距仪设计
本实验涉及到不同芯片间的通信,以及带有温度补偿的超声波测距,首先超声波测距会用STM32F103C8T6,与STM32F103ZET6通过CAN总线传输实时数据,
3
然后超声波模块采用最常用的超声波模块,如图4所示,温度传感器采用DS18B20数字温度传感器,如图5所示,具体电路图,详见附录一。
图1.2 超声波模块
图1.3 DS18B20数字温度传感器
(3) 简易伺服电机控制器设计
本实验中使用的是松下的伺服电机,MCU的 PA2和PA3引脚分别控制伺服电机的正反转(通过高低电平控制)和转速(通过PWM波的频率控制转速),学生可自行设计。
图1.4 伺服电机
图1.5 24VPWM输出
(4) 单相太阳能并网逆变器SPWM发生器设计
利用STM32的高级定时器8输出一组正确的互补的SPWM波送给逆变器模块
4
便可以利用示波器观察到正弦波。
图1.6 SPWM输出电路
图1.7 逆变器驱动电路
图1.8 逆变器全桥电路
5
第二章 Keil MDK5认识 (实验 1)
1 实验目的
(1) 学习使用Keil MDK5建立工程; (2) 熟悉Keil MDK5编程语言; (3) 学会使用Keil MDK5的软件仿真。
2 实验任务
(1) 安装Keil MDK5软件,安装芯片软件包,注册软件; (2) 新建工程文件;
(3) 下载程序,并进行软件仿真,观察程序运行结果。
3 实验说明
STM32所有系列的微控制器都可以在Keil MDK5下进行软件开发。Keil MDK,也称MDK-ARM,Realview MDK、I-MDK、uVision4 等。目前Keil MDK 由三家国内代理商提供技术支持和相关服务。
MDK-ARM软件为基于Cortex-M、Cortex-R4、ARM7、ARM9处理器设备提供了一个完整的开发环境。MDK-ARM专为微控制器应用而设计,不仅易学易用,而且功能强大,能够满足大多数苛刻的嵌入式应用。
4 实验步骤
(1) 安装Keil MDK5软件,安装芯片软件包,注册软件;
(2) 建立工程文件,选择芯片型号,加载系统文件、内核文件、标准外设驱动函数,添加系统启动文件;
(3) 设置头文件加载路径,需要配置一个全局的宏定义变量,定位到c/c++界面,然后填写“STM32F10X_HD,USE_STDPERIPH_DRIVER”到Define输入框里面;
(4) 编译整个工程,根据提示修改语法错误; (5) 下载程序;
(6) 选择仿真模式,利用模拟示波器观察程序运行结果。
6
MDK5软件的安装和注册
(1) 打开本课程所提供的资料,进入到MDK5文件夹。选中后Keil uvision5 MDK版,右键解压到当前文件夹。
图2.1 解压Keil uvision5 MDK版
(2) keygen是注册软件,mdk518就是Keil MDK5软件了,我们通常称之为Keil5。(后面为方便统称为Keil5)
图2.2 MDK5文件夹
(3) 开始安装Keil5,选mdk518,右键选择以管理员的身份运行此软件。(如果不这样做,可能相关驱动无法安装)
图2.3 开始安装
(4) 点击NEXT。
7
图2.4 点击NEXT
(5) 选中“I agree to…”,点击NEXT。
图2.5 接受服务条款
(6) 选定安装的路径,路径不可含有中文,必须是全英文。
8
图2.6 设置安装路径
(7) 随便填写信息,使用英文。
图2.7 填写用户基本信息
(8) 点击NEXT安装。
9
图2.8 等待安装
(9) 如果弹出是否要安装设备的界面,点击安装即可。(安装或者不安装都可以,因为到后面还要安装CH340的驱动)
图2.9 安装串口驱动
(10) 出现这个弹窗点击关闭即可。
图2.10 关闭弹窗
(11) 把MDK5弹出的所有窗口全部关闭。
10
图2.11 关闭所有弹窗
(12) 接下来,开始安装STM32F1系列的固件包。进入到此路径。第一个文件是固件库安装包,双击并根据提示进行安装。
图2.12 安装固件包
(13) 安装完成后,开始注册软件。回到桌面,左键选中,然后右键以管
理员的身份运行。(注意win7以上的系统一定要以管理员的身份打开此软件。否则,激活会失败。)点击file,然后选择“License Management…”。
图2.13 注册软件
(14) 复制CID码。
图2.14 CID码
11
(15) 左键选中keygen,右键单击选择以管理员的身份运行。
图2.15 运行keygen
图2.16 生成LIC
(16) 将生成的LIC复制粘贴到下面的那个框中,点击Add LIC。
图2.17 添加LIC
12
注意:如果没有注册机注册,软件将只能编译32KB以下的文件。
新建工程模板
(1) 新建文件夹Template,点击MDK的菜单:Project–>New Uvision Project,然后将路径定位到Template之下,在这个文件夹下面建立子文件夹USER,将工程文件就保存在USER文件夹中。工程命名为Template,点击保存。
图2.18 新建工程
(2) 接下来会出现一个选择CPU的界面,就是选择芯片型号。如图2.19所示,实训平台所使用的芯片型号为STM32F103ZET6,所以在这里选择STMicroelectronics–>STM32F1 Series–>STM32F103–>STM32F103ZET6。
图2.19 选择芯片类型
13
图2.20 选择芯片型号
(3) 点击OK,MDK会弹出Manage Run-Time Environment对话框,这是MDK5 新增的一个功能,在这个界面,可以添加自己需要的组件,从而方便构建开发环境,由于本实验并没有用到,直接关闭即可。
图2.21 添加组件对话框
(4) 现在USER目录下面包含两个文件夹和两个文件。Template.uvprojx是工程文件,非常关键,不能删除。Listings和Objects文件夹是MDK自动生成的文件夹,用于存放编译过程产生的中间文件,将其删除即可。
14
图2.22 删除Listings和Objects文件夹
(5) 接下来,在Template工程目录下面,新建3个文件夹CORE、OBJ以及TM32F10x_FWLib。CORE用来存放核心文件和启动文件,OBJ是用来存放编译过程文件以及hex文件,STM32F10x_FWLib文件夹用来存放ST官方提供的库函数源码文件。已有的USER目录除了用来放工程文件外,还用来存放主函数文件main.c,以及其他包括system_stm32f10x.c等。
图2.23 新建文件夹
(6) 将固件库包里面相关的启动文件复制到工程目录CORE文件夹。打开路径STM32F10x_StdPeriph_Lib_V3.5.0\\Libraries\\CMSIS\\CM3\\CoreSupport,将文件core_cm3.c和文件core_cm3.h复制到CORE文件夹中。然后在路径
STM32F10x_StdPeriph_Lib_V3.5.0\\Libraries\\CMSIS\\CM3\\DeviceSupport\\ST\\TM32F10x\\startup\\arm下面,将里面startup_stm32f10x_hd.s文件复制到CORE文件夹中。
将官方的固件库包里的源码文件复制到工程目录文件夹下面。打开官方固件库包,STM32F10x_StdPeriph_Lib_V3.5.0\\Libraries\\STM32F10x_StdPeriph_Driver 路径下,将目录下面的src和inc文件夹复制到STM32F10x_FWLib文件夹下面。src
15
存放的是固件库的.c文件,inc存放的是对应的.h文件,打开这两个文件目录过目一下里面的文件,每个外设对应一个.c文件和一个.h头文件。
图2.24 src文件夹
(7)STM32F10x_StdPeriph_Lib_V3.5.0\\Libraries\\CMSIS\\CM3\\DeviceSupport\\ST\\STM32F10x路径下,将里面的三个文件stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h,复制到USER目录下。
STM32F10x_StdPeriph_Lib_V3.5.0\\Project\\STM32F10x_StdPeriph_Template路径下的4个文件main.c、stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.h复制到 USER目录下面。
图2.25 USER文件夹
(8) 到此,需要的固件库相关文件已经复制到了工程目录下面,现在需要将这些文件加入到工程中。右键点击Template,选择Manage Components。
16
图2.26 Manage Components
(9) 下面往Group里面添加工程需要的文件。先选中FWLIB,然后点击右边的Add Files, 进入STM32F10x_FWLib/src路径下,将里面所有的文件选中(Ctrl+A),然后点击Add,然后点击Close可以看到Files列表下面已经包含添加的文件。这里需要说明一下,在平时的项目工程中,如果只用到了其中的某个外设,是不需要添加没有用到的外设库文件的。本实验中选择全部添加进来是为了后面方便,不用每次添加,当然这样的坏处是工程太大,编译起来速度慢,可以根据需要自行选择。
图2.27 添加src文件
17
图2.28 添加过后的FWLIB
(10) 用同样的方法,将Groups定位到CORE和USER下面,添加需要的文件。CORE下面需要添加的文件为core_cm3.c、core_cm3.h、startup_stm32f10x_hd.s (注意:默认添加的时候文件类型为.c, 也就是添加startup_stm32f10x_hd.s启动文件的时候,需要将选择文件类型更改为All files才能看得到这个文件),USER目录下面需要添加的文件为main.c、stm32f10x_it.c、system_stm32f10x.c。
图2.29 添加过后的CORE
18
图2.30 添加过后的USER
(11) 在编译工程之前要先选择编译中间文件编译后存放目录。方法是点击魔术棒,然后选择Output选项下面的“Select folder for objects…”,然后选择目录为新建的OBJ目录。这里需要注意,如不设置Output路径,那么默认的编译中间文件存放目录就是Keil MDK5自动生成的Objects目录和Listings目录。
图2.31 更改Output路径
(12) 添加头文件路径。对于任何一个工程,都需要把工程中引用到的所有头文件的路径都包含到进来。回到工程主菜单,点击魔术棒,出来一个弹窗,然后点击c/c++选项。然后点击Include Paths右边的按钮。弹出一个添加path的对话框,然后我们将如图2.33的3个目录添加进去。注意:Keil5只会在一级目录查找,所以如果你的目录下面还有子目录,记得path一定要定位到最后一级目录。然后
19
点击OK。
图2.32 添加头文件路径
(13) 编译工程。可以看到出现很多错误。这是因为3.5版本的库函数在配置和选择外设的时候通过宏定义来选择的,所以需要配置一个全局的宏定义变量。打开c/c++界面,然后填写“STM32F10X_HD,USE_STDPERIPH_DRIVER”到Define输入框里面。(注意:实训平台使用的时STM32F103ZET6位大容量芯片,所以填写STM32F10X_HD,如果是中容量那么STM32F10X_HD修改为 STM32F10X_MD,小容量修改为STM32F10X_LD。)然后点击OK。
图2.33 通过宏定义选择外设
(14) 这次在编译之前,打开工程USER下面的main.c,复制下面一段代码到
20
main.c覆盖已有代码,然后进行编译。(记得在代码的最后面加上一个回车,否则会有警告,这是软件本身的bug,与代码无关)可以看到,这次编译已经成功了。 #include \"stm32f10x.h\"
void Delay(u32 count) //延迟函数 { u32 i=0; for(;i GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MH GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIOB.5 GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高 GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化 GPIOE.5 GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高 while(1) { GPIO_ResetBits(GPIOB,GPIO_Pin_5); //PB.5 输出低 Delay(3000000); //延迟 GPIO_SetBits(GPIOB,GPIO_Pin_5); Delay(3000000); } } 21 图2.34 编译工程 可以看到0个错误,0个警告。到此工程就建立完成了。 图2.35 编译结果 到了这里建立工程文件结束,可以明显感受到建立工程模版的步骤十分复杂。所以在以后实验里,可以直接拿本实验中建好的工程模版使用。 软件仿真 (1) 单击魔术棒,单击Target,将晶振改为8MHZ。 图2.36 设置晶振 (2) 单击Debug选项,选择:Use Simulator,使用软件仿真。选择:Run to main(),即跳过汇编代码,直接跳转到main函数开始仿真。设置下方的:Dialog DLL分别为:DARMSTM.DLL和TARMSTM.DLL,Parameter均为:-pSTM32F103ZE,用于设置支持STM32F103ZE的软硬件仿真(即可以通过Peripherals选择对应外设的对话框观察仿真结果)。最后点击OK,完成设置。 22 图2.37 软件仿真配置 (3) 配置完成后点击OK。打开软件仿真。 图2.38 开始仿真 (4) 调用示波器。 图2.39 打开示波器 (5) 先点击红框内的小方框,然后点击Setup输入PORTB.5点击回车: 图2.40 添加监控GPIOB.5 出现下图所示,点击Close。 23 图2.41 添加成功 (6) 点击运行。 图2.42 开始运行 (7) 运行十秒钟左右点击停止。 图2.43 仿真结束 (8) 观察波形,测量高电平和低电平的时间间隔。如果观察不到波形。可以在上面找到Auto,点击就会自动调整。(上方红色小框) 24 图2.44 观察示波器 (9) 比较测量的时间与设定的时间的误差并分析其原因。 下载程序到实训平台 (1) 接上J-Link V8,并把Jlink插到实训平台上,打开Options for Target选项卡,在Debug栏选择使用J-LINK/ J-TRACE Cortex,如下图。 图2.45 选择JLINK下载 (2) 点击Settings,进入下图所示界面。 25 图2.46 选择下载模式 (3) 如果提示keil cannot load driver JL2CM3.dll,需要配置系统环境。自行百度进入设置系统环境变量的方法。(出现这种情况大部分是由于在电脑的用户名为中文用户名) 图2.47 更改环境变量 (4) 填写完后,点击确定即可进入下一步。然后:点击Flash Download,进行如下设置 26 图2.48 下载配置 (5) 点击确定,返回之前那个界面然后,点击Utilities按钮,在设置完之后,点击OK,回到IDE界面。 图2.49 选择Use Debug Driver (6) 为了能直观的观察效果,我们为PB5接一个灯,这样就可以控制LED灯的闪烁。也可使用示波器观察实际输出波形。 27 第三章 系统滴答定时器的应用 (实验 2) 1 实验目的 (1) 理解滴答定时器SysTick定时器的工作原理; (2) 学会使用中断函数。 2 实验任务 (1) 编写SysTick定时器初始化程序; (2) 编写SysTick定时器的中断服务函数。 3 实验说明 (1) SysTick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有SysTick定时器。SysTick定时器就是系统滴答定时器,一个24位的倒计数定时器,计到0时,将从RELOAD寄存器中自动重新装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。SysTick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用SysTick做UCOS心跳时钟。 (2) SysTick_CLKSourceConfig()// SysTick时钟源选择misc.c文件中 SysTick_Config(uint32_t ticks)//初始化SysTick,时钟为HCLK,并开启中断。 以上就是滴答定时器常用的库函数了,第一个函数为滴答器选择时钟源。时钟源的选择有以下两种:内部时钟源SysTick_CLKSource_HCLK和以及外部时钟源SysTick_CLKSource_HCLK_Div8,后者为前者的八分之一。 假如SysTick_CLKSource_HCLK为72MHz,那么SysTick_CLKSource_HCLK _Div8为9MHz。前面提到滴答定时器其实是一个24位递减的定时器。第二个函数用来初始化滴答定时器,也就是为其设置初值。 28 图3.1 时钟树 4 预习要求 (1) 了解STM32中的5个时钟源及时钟的分配; (2) 了解滴答定时器中寄存器的功能。 5 实验步骤 (1) 打开工程文件; (2) 编写程序代码; (3) 编译无误后进行仿真; (4) 用J-Link将程序下载到实训平台上,观察实验现象。 硬件设计 图3.2 电路原理图 软件设计 29 (1) 将实验一建立的工程模板复制一份,并重命名为“系统滴答定时器”。打开工程文件。 图3.3 USER文件夹 (2)将main.c中的程序代码替换。 #include \"sys.h\" #include \"usart.h\" //void SysTick_Handler(void) //该函数在stm32f103_it.h中定义,在stm32f103_it.c中编写 int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); //使能PPE端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOE, &GPIO_InitStructure); //根据设定参数初始化 GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高 //系统滴答定时器的配置 SysTick->LOAD=360000; SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; while(1); } (2)编写滴答定时器的中断服务函数。 打开项目树USER下的stm32f10x_it.c,找到void SysTick_Handler(void);函数,并在该函数下编写中断服务函数,具体代码如下 void SysTick_Handler(void) { 30 } static u8 aa; if(aa>200) aa=0; else aa++; if(aa>100) { GPIO_ResetBits(GPIOE,GPIO_Pin_5); } else { GPIO_SetBits(GPIOE,GPIO_Pin_5); } 仿真与调试 编译程序,没有错误后进行软件仿真。然后下载程序,点击魔术棒,切换下载方式。用J-Link将程序下载到实训平台上,观察实验现象。 31 第四章 跑马灯实验 (实验 3) 1 实验目的 (1) 了解通用IO口的输出类型和初始化过程,学会对GPIO库函数的使用; (2) 掌握基本IO口的使用; (3) 利用GPIO函数和延时函数实现对LED灯的交替闪烁,实现类似跑马灯的效果。 2 实验任务 (1) 编写程序,实现对LED1~LED8的轮流点亮; (2) 仿真调试,调整延时时间,利用仿真示波器观察延时时间长短; (3) 下载程序,观察跑马灯运行状况。 3 实验说明 本实验将要实现的是控制实训平台上的8个LED灯实现一个类似跑马灯的效果,LED通过控制IO口的高低电平工作,因此实验的关键在于如何控制STM32的IO口输出。 4 预习要求 (1) 初始化IO口包括哪些基础设置。 (2) GPIO的输入输出模式。 5 实验步骤 (1) 在实训平台上将IO口与LED(LED1~LED8)连接; (2) 复制工程模板文件夹,新建led.c和led.h文件,并将新建文件加入工程中; (3) 编写led.h文件,声明void LED_Init(void)初始化函数,宏定义LED1~LED8; (4) 编写led.c文件,建立void LED_Init(void)初始化函数,实现对LED灯用到的IO端口的配置,配置为推挽输出,速度为50MHZ; (5) 编写main()函数,实现对LED1~LED8的轮流点亮; (6) 软件仿真,调整延时时间,利用仿真示波器观察延时时间长短; 32 (7) 下载程序,观察跑马灯的运行状况。 硬件设计 本实验用到的硬件只有LED(LED1 ~ LED8)。电路实训平台上默认是未连接好的,所以在硬件上需要根据自己的需要将其与MCU进行连接。注意:LED的硬件为共阳极连接,需将IO口连接端置为低电平才能点亮。LED模块原理图如4.1所示: 图4.1 LED模块原理图 软件设计 (1) 新建文件,命名为跑马灯实验。复制粘贴之前的Template工程。 图4.2 跑马灯实验文件 (2) 新建LED文件。在跑马灯实验文件夹下面新建一个HARDWARE的文件夹,用来存储与硬件相关的代码, 然后在HARDWARE文件夹下新建一个LED文件夹,用来存放与LED相关的代码,如图4.3所示。 图4.3 新建HARDWARE以及LED文件夹 33 (3) 新建led.c和led.h文件。打开USER文件夹下的Template. uvprojx,将其重命名为LED. Uvprojx。点击 新建一个文件保存在HARDWARE->LED文 件夹,命名为led.c。按照同样的方法,再新建一个led.h文件,保存在LED文件夹。 图4.4 修改文件名 图4.5 新建及保存文件 图4.6 新建led.c及led.h文件 34 (4) 将HARDWARE文件添加到工程当中。首先右键单击Template文件夹,点击“Manage Project Items...”选项,在打开的新窗口中,点击添加按钮,在光标闪动处添加HARDWARE文件名,然后点击任意空白处即可保存文件。 图4.7 打开Manage Project Items 图4.8 添加HARDWARE文件 (5) 将led.c文件加入工程。找到“实验3 跑马灯实验”下的HARDWARE文件,打开文件夹,找到LED文件下的led.c文件,双击led.c文件或选中led.c文件后(文件名一栏出现led.c的字样)点击“add”按钮,然后点击“close”按钮即可。此时可以看到,“Files:”一栏中出现“led.c”文件,然后点击“OK”按钮即可保存。 35 图4.9 打开HARDWARE文件 图4.10 添加led.c文件到HARDWARE文件 图4.11 成功添加led.c文件 36 添加成功后,可以看到工程目录中出现了HARDWARE文件,点击左侧加号按钮可以看到HARDWARE文件夹下成功添加了led.c文件。 图4.12 文件添加成功 图4.13 添加led.h路径 图4.14 点击LED文件夹 图4.15 点击OK添加成功 37 图4.16 添加led.h路径 (6) 编写led.h文件 图4.17 led.h文件 #ifndef ...#endif必须要配合使用。#ifndef的意思是如果没有定义,即判断如果没有定义__LED_H,则会进行下面的定义,防止重复定义。 (7) 编写led.c文件 首先配置外设时钟。在配置STM32外设的时候,任何时候都要先使能该外设的时钟。GPIO是挂载在APB2总线上的外设,在固件库中对挂载在APB2总线上的外设时钟使能是通过函数RCC_APB2PeriphClockCmd()来实现的。对于这个入口参数设置,这里需要说明的是,因为本试验用到的IO口初始化参数都是设置在结构体变量GPIO_InitStructure中,而且IO口的模式和速度都一样,所以只用初始化一次。IO口初始化后加入一行代码:GPIO_SetBits(GPIOE, GPIO_Pin_5)的作用是在初始化中将IO口输出设置为高。最后,保存led.c。 38 图4.18 led.c文件 操作IO口输出高低电平有三种方法。 第一种方法:位带操作 通过位带操作PE0输出高低电平从而控制LED1。 #define LED1 PEout(0) //位带操作,即LED1代表的是PE0端口 LED1=1; //通过位带操作控制LED1的引脚PE0输出高电平; LED1=0; //通过位带操作控制LED1的引脚PE0输出低电平; 第二种方法:库函数操作 GPIO_SetBits(GPIOE, GPIO_Pin_0); //设置GPIOE.0输出1,等同LED1=1; GPIO_ResetBits (GPIOE, GPIO_Pin_0); //设置GPIOE.0输0,等同LED1=0; 第三种方法:寄存器操作 GPIOB->ODR|=1<<5;//设置GPIOE.0输出1,等同LED1=1; GPIOB->ODR|&=~(1<<5);//设置GPIOE.0输出0,等同LED1=0; 对于上面三种方法,根据个人习惯来选择一种即可。在IO口速度没有太大要求的情况下效果都是一样的。本次试验通过在固件库中设置ODR寄存器的值 39 来控制IO口的输出状态: void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); 该函数一般用来一次性将一个GPIO的多个端口进行设置。 (8) 编写main()函数。首先打开USER文件夹内的main.c,删除main.c里面的所有代码,接着开始写主程序,如下图所示。 其中,GPIO_Write函数内的第二个参数需要注意一下,因为实训平台上的LED灯为低电平有效,只有当端口输出为零时,LED灯才会被点亮,所以i需要取反,取反符号为“~”。当i=00000001时,取反的结果为11111110,这样就只有最低位(PE0)控制的灯被点亮。 图 4.19 主函数 仿真与下载 编译无误后使用软件仿真示波器观察延时时间,观察是否与设定延时时长相符合,再根据软件仿真的结果,将程序下载到实训平台观察运行结果。 40 第五章 按键输入实验 (实验 4) 1 实验目的 (1) 学会按键的扫描输入; (2) 理解按键消抖的原因,学会按键消抖方法。 2 实验任务 (1) 建立KEY.H文件,声明void KEY_Init(void)初始化函数,声明u8 KEY_Scan(u8 mode)函数,宏定义KEY0、KEY1和WK_UP; (2) 编写KEY.C文件,建立void KEY_Init(void)初始化函数,实现对按键连接IO端口的配置; (3) 编写main()函数,调用按键扫描程序,根据不同的按键实现对LED0和LED1的点亮。 3 实验说明 KEY0和KEY1是低电平有效的,而KEY_UP是高电平有效的,并且外部都没有上下拉电阻,所以,需要在STM32F1内部设置上下拉。 同时需要把LED及按键接入到MCU的引脚上,连接方式按照软件和IO配置情况具体分配。 4 预习要求 (1) 理解按键扫描输入函数。 5 实验步骤 (1) 建立按键输入实验工程文件; (2) 编写程序; (3) 编译无误后进行软件仿真; (4) 用J-Link将程序下载到实训平台上,观察实验现象。 硬件设计 41 图5.1 LED和KEY电路原理图 软件设计 (1) 新建文件夹并命名为按键输入实验,复制粘贴之前的实验文件,将工程文件改名为KEY.uvprojx,同时添加key.c和key.h文件至工程中。 (2) led.h和led.c代码如下图: 图5.2 led.h文件 图5.3 led.c文件 (2) 首先打开key.h文件,以KEY0为例,先使用GPIO_ReadInputDataBit()函数读取PE6端口的输入电平,再定义KEY0按下后KEY_Scan()返回的键值。 #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_6)//读取按键0 再分别对其他两个按键进行宏定义 对key.c内的函数进行声明 42 void KEY_Init(void);//IO初始化 u8 KEY_Scan(void);//按键扫描函数 图5.4 key.h文件 (3) 再打开key.c文件,添加初始化函数void KEY_Init(void),在该函数内配置。先使用RCC_APB2PeriphClockCmd()使能要使用端口的时钟,再配置各按键的输入模式KEY0、KEY1为上拉输入,WK_UP为下拉输入。 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成下拉输入 (输入模式不用配置IO口速度,只需配置GPIO_Pin,GPIO_Mode即可) 再添加扫描按键的函数void KEY_Scan(void),KEY_Scan函数是用来扫描这3个IO口是否有按键按下。key_up为静态变量,函数执行完成后不释放,key_up的值不变。该函数基本思路为:先通过if语句判断是否有按键按下并且通过key_up的值来判断上次按下后,按键是否已释放。如果有按键按下同时key_up=0,则延时20ms后再判断是哪个按键按下了,并返回对应的值。 其中,延时一段时间是为了消抖。通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。按键必须要消抖,有机械消抖和软件消抖等方式,这里采用延时来避免抖动干扰; 43 图5.5 key.c文件 (4) 主函数的内容是进行一系列的初始化操作,然后在死循环中不断调用按键扫描函数KEY_Scan()扫描按键值,最后根据按键值控制LED的翻转。通过LED,可以观察到按键是否工作。 图5.6 main.c文件 下载与调试 程序编译无误后下载到实训平台进行验证。 44 第六章 串口实验 (实验 5) 1 实验目的 (1) 理解串口的工作原理; (2) 学会STM32通过串口和计算机通信。 2 实验任务 (1) 调用usart.c文件中相关函数及变量; (2) 通过计算机串口软件发送LED灯闪烁时间间隔实现串口对LED灯频率的控制。 3 实验说明 STM32串口简介 串口是MCU的重要外部接口,同时也是软件开发重要的调试手段。现在基本上所有的MCU都会带有串口,STM32自然也不例外。 本实验将主要从库函数操作层面结合寄存器的描述,介绍如何设置串口,以达到最基本的通信功能,并且介绍如何通过USB串口和电脑通信。串口设置的一般步骤可以总结为如下几个步骤: 1) 串口时钟使能,GPIO时钟使能 2) 串口复位 3) GPIO端口模式设置 4) 串口参数初始化 6) 使能串口 7) 编写中断处理函数 与串口基本配置直接相关的几个固件库函数和定义主要分布在 stm32f10x_usart.h和stm32f10x_usart.c文件中。关于串口更详细的介绍,请参考《STM32 参考手册》第516页至548页,通用同步异步收发器一章。 4 预习要求 (1) 串口参数包括哪些?分别有什么作用? (2) 复用功能下的串口GPIO模式有哪些?该如何配置? 45 4 实验步骤 (1) 实训平台上PA9和PA10已经与TXD、RXD连接,串口硬件配置完成; (2) 将LED端口与对应IO口用导线连接; (3) 用数据线将串口与电脑的USB接口连接; (4) 复制上一个实验工程修改名称并保存为USART实验,并将工程文件名称修改为USART. uvprojx; (5) 编写main()函数,程序编译成功后下载程序到实训平台; (6) 打开串口调试助手XCOM V2.0,改变延时时间观察LED灯的变化。 硬件设计 图6.1 PE5连接LED 软件设计 (1) 查看usart.c的代码。在SYSTEM文件下双击usart.c,如图6.2所示。 图6.2 uart_init()函数 46 图6.3 USART1_IRQHandler()函数 对于NVIC中断优先级管理,参考STM32F1开发指南4.5中断优先级管理。USART1_IRQHandler中断相应函数的名字是不能随便定义的,一般都遵循MDK定义的函数名。 void USART1_IRQHandler(void) //串口1中断服务程序 图6.4 程序语句 第一句表示发送一个字节到串口。第二句表示在发送一个数据到串口之后,要检测这个数据是否已经被发送完成。USART_FLAG_TC是宏定义的数据发送完成标识符。 USART_RX_BUF[USART_REC_LEN];//接收缓冲,最大USART_REC_LEN 个字节,末字节为换行符; 当完成一次接收后,接收数据回存储在接收缓冲区中,可以直接调用读取使用。 (2) 查看usart.h的代码。在usart.c组下双击usart.h,如图6.5所示。 47 图6.5 usart.h文件 (3) main()函数参考程序 #include \"led.h\" #include \"delay.h\" #include \"sys.h\" #include \"usart.h\" /* 串口实验: 本程序可实现基本实验内容, 扩展试验内容自行编写;*/ int main(void) { u16 t; u16 len; u16 rx[10]; u16 times=300; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //LED初始化 while(1) { if(USART_RX_STA&0x8000) { len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度 printf(\"\\r\\n您发送的消息为:\\r\\n\\r\\n\"); for(t=0;t while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束 } /******调试过程,去掉注释可以清楚的看到串口接收的数据情况****** for(t=0;t } printf(\"\\r\\n\"); for(t=0;t 下载与调试 程序编译无误后下载到实训平台,并与电脑通过串口连接:打开串口调试助手XCOM V2.0。选择波特率与代码中一致,在下方输入框输入延时时间点击发送观察LED灯的变化。 49 第七章 外部中断实验 (实验 6) 1 实验目的 (1) 理解外部中断输入的工作原理; (2) 理解IO口与中断线的映射关系; (3) 学会使用外部中断服务函数。 2 实验任务 (1) 编写外部中断服务函数; (2) 初始化外部中断,配置触发条件,配置中断分组; (3) 控制LED灯亮灭状态或闪烁状态。 3 实验说明 (1) STM32外部IO口有中断功能,通过中断的功能,达到实验4的效果,即:通过板载的3个按键,控制板载的三个LED的亮灭。本实验的代码主要分布在固件库的stm32f10x_exti.h和stm32f10x_exti.c文件中。 STM32的每个IO都可以作为外部中断的中断输入口,这点也是STM32的强大之处。STM32F103的中断控制器支持19个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有的触发和屏蔽设置。STM32F103的19个外部中断为: 线0~15:对应外部IO口的输入中断 线16:连接到PVD输出 线17:连接到RTC闹钟事件 线18:连接到USB唤醒事件 从上面可以看出,STM32供IO口使用的中断线只有16个,但是STM32的IO口却远远不止16个,GPIO的管脚GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线0~15。这样每个中断线对应了最多7个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到1个IO口上,这样就需要通过配置来决定对应的中断线配置到哪个GPIO上了。下面是中断线的映射关系图: 50 图7.1 映射关系图 配置GPIO与中断线的映射关系的函数GPIO_EXTILineConfig()来实现的: void GPIO_EXTILineConfig(uint8_t GPIO_PortSource,uint8_t GPIO_PinSource) 该函数将GPIO端口与中断线映射起来,使用范例是: GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2); 将中断线2与GPIOE映射起来,那么很显然是GPIOE.2与EXTI2中断线连接了。 接下来就要设置该中断线上中断的初始化参数了。 中断线上中断的初始化是通过函数EXTI_Init()实现的。EXTI_Init()函数的定义是:void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); STM32 的外设的初始化都是通过结构体来设置初始值的。结构体EXTI_InitTypeDef 的成员变量: typedef struct { uint32_t EXTI_Line; //中断线的标号,取值范围为 EXTI_Line0~EXTI_Line15配置的是某个中断线上的中断参数。EXTIMode_TypeDef EXTI_Mode; //中断模式,可选值为中断 EXTI_Mode_Interrupt和事件EXTI_Mode_Event。 EXTITrigger_TypeDef EXTI_Trigger; //触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发EXTI_Trigger_Rising,或者任意电平(上升沿 51 和下降沿)触发EXTI_Trigger_Rising_Falling。 FunctionalState EXTI_LineCmd; //使能中断线 }EXTI_InitTypeDef; 设置好外部中断的初始化参数,还要设置NVIC中断优先级。这个在前面已经讲解过。 配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是在MDK中事先有定义的。STM32的IO口外部中断函数只有6个,分别为: EXPORT EXTI0_IRQHandler EXPORT EXTI1_IRQHandler EXPORT EXTI2_IRQHandler EXPORT EXTI3_IRQHandler EXPORT EXTI4_IRQHandler EXPORT EXTI9_5_IRQHandler EXPORT EXTI15_10_IRQHandler 中断线0-4每个中断线对应一个中断函数,中断线5-9共用中断函数 EXTI9_5_IRQHandler,中断线10-15共用中断函数EXTI15_10_IRQHandler。在编写中断服务函数的时候会经常使用到两个函数,第一个函数是判断某个中断线上的中断是否发生(标志位是否置位): ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);//判断中断是否发生 这个函数一般使用在中断服务函数的开头判断中断是否发生。另一个函数是清除某个中断线上的中断标志位: void EXTI_ClearITPendingBit(uint32_t EXTI_Line);//清除某个中断线上的中断标志位 这个函数一般应用在中断服务函数结束之前,清除中断标志位。常用的中断服务函数格式为: void EXTI3_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line3)!=RESET) //判断某个线上的中断是否发生 { 52 中断逻辑… EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位 } } (2) 使用IO口外部中断的一般步骤 1) 初始化IO口为输入; 2) 开启AFIO时钟; 3) 设置IO口与中断线的映射关系; 4) 初始化线上中断,设置触发条件等; 5) 配置中断分组(NVIC),并使能中断; 6) 编写中断服务函数。 通过以上几个步骤的设置,就可以正常使用外部中断。 4 预习要求 (1) 了解中断线与IO口的对应关系。 5 实验步骤 (1) 打开工程文件; (2) 编写程序; (3) 编译无误后进行仿真; (4) 用J-Link将程序下载到实训平台上,观察实验现象。 硬件设计 图7.2 电路原理图 软件设计 53 (1) 新建文件夹命名为外部中断实验,复制粘贴之前的串口实验文件,将工程文件名修改为EXTI.uvprojx,并在HARDWARE中添加exti.c文件和exti.h文件。 (2) 进行按键及LED的初始化函数编写,复制的工程文件中包含这两个初始化函数,可根据需要自行修改。 (3) 编写exti.h文件 图7.3 exti.h文件 (4) 编写exti.c文件 图7.4 exti.c文件 (5) 编写中断服务函数。 54 图7.5 中断服务函数 (6) 编写main函数。 图7.6 main函数 下载与调试 程序编译无误后下载到实训平台进行验证。 55 第八章 定时器中断实验 (实验7) 1 实验目的 (1) 掌握实现定时器中断的方法; (2) 学习利用定时器中断编写延时函数。 2 实验任务 (1) 设置定时器时钟,自动重装载值,分频系数和计数方式; (2) 设置定时器中断优先级; (3) 通过编写延时函数实现定时器中断。 3 实验说明 STM32的通用定时器是由一个通过可编程预分频器(PSC)驱动的16位自动装载计数器(CNT)构成。STM32的通用定时器的用途:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。使用定时器预分频器和 RCC时钟控制器预分频器,可以使脉冲长度和波形周期在几个微秒到几个毫秒间调整。 STM32F10x的通用TIMx(TIM2、TIM3、TIM4和TIM5)定时器功能包括: (1) 6位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。 (2) 16位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间的任意数值。 (3) 4个通道(TIMx_CH1~4),这些通道可以用来作为: A.输入捕获 B.输出比较 C.PWM生成(边缘或中间对齐模式) D.单脉冲模式输出 (4) 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用1个定时器控制另外一个定时器)的同步电路。 (5) 如下事件发生时产生中断/DMA: A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/ 56 外部触发) B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) C.输入捕获 D.输出比较 E.支持针对定位的增量(正交)编码器和霍尔传感器电路 F.触发输入作为外部时钟或者按周期的电流管理 定时器的时钟来源有4个: (1) 内部时钟(CK_INT) (2) 外部时钟模式1:外部输入脚(TIx) (3) 外部时钟模式2:外部触发输入(ETR) (4) 内部触发输入(ITRx):使用A定时器作为B定时器的预分频器(A为B提供时钟)。 这些时钟,具体选择哪个可以通过TIMx_SMCR寄存器的相关位来设置。这里的CK_INT时钟是从APB1倍频来的,除非APB1的时钟分频数设置为1,否则通用定时器TIMx的时钟是APB1时钟的2倍,当APB1的时钟不分频的时候,通用定时器TIMx的时钟就等于APB1的时钟。这里还要注意的就是高级定时器的时钟不是来自APB1,而是来自APB2。 本实验使用定时器3产生溢出中断,在中断服务函数里面翻转LED上的电平,来指示定时器中断的产生。定时器相关的库函数主要集中在固件库文件stm32f10x_tim.h和stm32f10x_tim.c文件中。 4 预习要求 (1) 三种定时器有什么区别? (2) 通用定时器有哪几种计数模式?了解每个模式的特点。 (3) 了解通用定时器的工作过程。 5 实验步骤 (1) 在实训平台上将PE4和PE5分别连接LED灯; (2) 复制上一个实验工程修改名称并保存为定时器中断实验; (3) 新建timer.c和timer.h文件,添加至工程中; (4) 编写timer.h文件,声明定时器3初始化函数; 57 (5) 编写timer.c文件,编写定时器3初始化函数,设置分频系数、计数方式、自动重装载计数周期值和时钟分频因子; (6) 编写main函数,程序编译正确; (7) 下载至实训平台调试验证。 硬件设计 图8.1 LED电路原理图 软件设计 (1) 新建文件夹并命名为定时器中断实验,复制粘贴之前的实验文件,将工程文件改名为TIMER.uvprojx,同时添加timer.c和timer.h文件至工程中。 (2) 编写timer.h文件。 图8.2 timer.h文件 (3) 编写timer.c文件。 a. 让TIM3时钟使能 TIM3挂载在APB1下,通过APB1总线下的使能函数来使能。 图8.3 时钟使能 b. 初始化定时器参数, 设置自动重装值,分频系数,计数方式 库函数中定时器的初始化参数是通过初始化函数TIM_TimeBaseInit实现的, Void TIM_TimeBaseInit(TIM_TypeDef*TIMx, TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct 58 第一个参数是确定哪一个定时器,第二个参数是定时器初始化参数结构体指针 结构体类型为TIM_TimeBaseInitTypeDef,这个结构体的定义为: 图8.4 结构体定义 这个结构体一共有5个成员变量,对于通用定时器只有前面4个参数有用,最后一个参数TIM_RepetitionCounter在高级定时器才会用到。 c. 设置TIM3_DIER允许更新中断 寄存器的相应位使能更新中断,在库函数里面定时器中断使能是通过TIM_ITConfig函数来实现的: void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); 第一个参数是选择定时器,取值为TIM1~TIM17。 第二个参数非常关键,是用来指明使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断TIM_IT_Update,触发中断TIM_IT_Trigger,以及输入捕获中断等等。 第三个参数是设定失能还是使能。 最终函数为TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); d. TIM3中断优先级设置 在定时器中断使能之后,因为要产生中断,必不可少的要设置NVIC相关寄存器,设置中断优先级。 图8.5 中断优先级设置 e. 使能TIM3 配置完后要开启定时器,通过TIM3_CR1的CEN位来设置。在固件库里面使能定时器的函数是通过TIM_Cmd函数来实现的: 59 void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState) 此时定时器3初始化函数为: 图8.6 初始化函数 TIM3_Int_Init()函数就是上边介绍的几个步骤,该函数的2个参数用来设置TIM3的溢出时间。系统初始化的时候在默认的系统初始化函数SystemInit函数里面已经初始化APB1的时钟为2分频,所以APB1的时钟为36M,而从STM32的内部时钟树图得知:当APB1的时钟分频数为1的时候,TIM2~7的时钟为APB1的时钟,而如果APB1的时钟分频数不为1,那么TIM2~7的时钟频率将为APB1时钟的两倍。因此,TIM3的时钟为72M,再根据我们设计的arr和psc的值,就可以计算中断时间了。计算公式如下: Tout= ((arr+1)*(psc+1))/Tclk; 其中: Tclk:TIM3的输入时钟频率(单位:Mhz) Tout:TIM3的溢出时间(单位:us) f. 编写中断服务函数 通过该函数来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,这里使用的是溢出中断,所以在状态寄存器SR的最低位。在处理完中断之后应该向TIM3_SR的最低位写0,来清除该中断标志。 在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是: ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t) 60 该函数的作用是判断定时器TIMx的中断类型,TIM_IT是否发生中断。 固件库中清除中断标志位的函数是: void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT) 该函数的作用是清除定时器TIMx的中断TIM_IT标志位。 中断服务函数为: 图8.7 中断服务函数 在图8.6中断服务函数中,中断后的指令为控制LED的翻转。每次中断后,判断TIM3的中断类型,如果中断类型正确(溢出中断),则执行LED的取反。 图8.8 主函数 (4) 编写main()函数 这里的代码和之前大同小异,此段代码对TIM3进行初始化后,进入死循环等待TIM3溢出中断,当TIM3_CNT的值等于TIM3_ARR的值的时候,就会产生TIM3的更新中断,然后在中断里面取反LED1,TIM3_CNT再从0开始计数。根据上面的公式,我们可以算出中断溢出时间为500ms。 Tout=((4999+1)*(7199+1))/72=500000us=500ms 下载与调试 程序编译无误后下载到实训平台,观察其运行结果是否与编写的程序一致。如果实验无误,我们将看到LED2不停闪烁(每100ms闪烁一次),而LED1也不停地闪烁,但是闪烁时间比较慢。 61 第九章 PWM输出实验 (实验 8) 1 实验目的 (1) 理解定时器的PWM原理; (2) 学会TIM定时器的使用。 2 实验任务 (1) 开启TIM时钟,配置定时器通道对应IO口为复用输出; (2) 利用定时器的PWM输出功能产生波; (3) 通过调节占空比调节LED的亮度。 3 实验说明 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。 STM32的定时器除了TIM6和7。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出。要使STM32的通用定时器TIMx产生PWM输出,除了上一实验的寄存器外,我们还会用到3个寄存器,分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。 本实验仅利用TIM3的CH2产生一路PWM输出。通过重映射TIM_CH2到PB5上,由TIM_CH2输出PWM来控制LED1的亮度。 4 预习要求 (1) 了解PWM的作用; (2) 了解TIM定时器的使用方法。 5 实验步骤 (1) 在实训平台上将PB5连接LED灯; (2) 编写timer.c与timer.h文件,实现通过重映射TIM_CH2到PB5上,由 62 TIM_CH2输出PWM来控制LED1的亮度。 (3) 编写main文件,编译成功; (4) 程序编译无误后下载到实训平台,观察LED亮度的变化:通过示波器观察输出的波形。 硬件设计 图9.1 LED电路原理图 软件设计 (1) 新建文件夹并命名为PWM输出实验,复制粘贴上一章的实验文件,将工程文件改名为PWM.uvprojx。 (2) timer.h文件只需再添加TIM3_PWM_Init的声明即可。 (3) 编写timer.c文件。 a. 开启TIM3时钟以及复用功能时钟置,配置PB5为复用输出 要使用TIM3,必须先开启TIM3的时钟,使能GPIO外设和AFIO复用功能模块时钟。因为TIM3_CH2通道将重映射到PB5上,此时还要配置PB5为复用输出。 库函数使能TIM3时钟的方法是: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟 库函数设置AFIO时钟的方法是: RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用时钟使能 GPIO初始化为复用推挽输出: GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 63 图9.2 开启TIM3时钟 b. 设置TIM3_CH2重映射到PB5上 因为TIM3_CH2默认是接在PA7上的,所以需要设置TIM3_REMAP为部分重映射(通过AFIO_MAPR配置),让TIM3_CH2重映射到PB5上面。 库函数函数里面设置重映射的函数是: Void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState); STM32重映射只能重映射到特定的端口。第一个入口参数可以理解为设置重映射的类型,比如TIM3部分重映射入口参数为GPIO_PartialRemap_TIM3。 TIM3部分重映射的库函数实现方法是: GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); 图9.3 重映射 c. 初始化TIM3,设置TIM3的ARR和PSC 开启TIM3的时钟之后,要设置ARR和PSC两个寄存器的值来控制输出PWM 的周期。当PWM周期太慢(低于50Hz)的时候,就会明显感觉到闪烁了。所以PWM周期在这里不宜设置的太小。 库函数通过TIM_TimeBaseInit函数实现的。调用的格式为: TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx 图9.4 初始化TIM3 d. 设置TIM3_CH2的PWM模式及通道方向,使能TIM3的CH2输出 要设置TIM3_CH2为PWM模式(默认是冻结的),因为LED是低电平亮,要达到当CCR2的值小的时候,LED就暗,CCR2值大的时候,LED就亮的目的,可以通过配置TIM3_CCMR1的相关位来控制TIM3_CH2的模式。在库函数中,PWM 通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样。这里使用通道2,所以使用的函数是TIM_OC2Init(): Void TIM_OC2Init(TIM_TypeDef* TIMx,TIM_OCInitTypeDef* TIM_OCInitStruct) TIM_OCInitTypeDef相关的几个成员变量 图9.5 TIM_OCInitTypeDef相关的几个成员变量 65 参数TIM_OCMode设置模式是PWM还是输出比较,这里是PWM模式。 参数TIM_OutputState用来设置比较输出使能,也就是使能PWM输出到端口。 参数TIM_OCPolarity用来设置极性是高还是低。 其他的参数TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState和 TIM_OCNIdleState是高级定时器TIM1和TIM8才用到的; 图9.7 设置TIM3_CH2的PWM模式及通道方向 e. 在完成以上设置了之后,需要使能TIM3: TIM_Cmd(TIM3, ENABLE); //使能TIM3 图9.8 使能TIM3 f. 最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而通过修改TIM3_CCR2则可以控制CH2的输出占空比。继而控制LED的亮度。 在库函数中,修改TIM3_CCR2占空比的函数是: void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2); 对于其他通道,分别有一个函数名字,函数格式为TIM_SetComparex(x=1,2,3,4)。 通过以上6个步骤,就可以控制TIM3的CH2输出PWM波了。(注意:在配置AFIO相关寄存器的时候,必须先开启辅助功能时钟。) 66 (4) 编写main()函数 图9.1 main.c文件 在本实验的main函数中初始化的是TIM3_PWM_Init(9,0);而定时器溢出中断中初始化的是TIM3_Int_Init(4999,7199)。 从死循环函数可以看出,将led0pwmval这个值设置为PWM比较值,也就是通过led0pwmval来控制PWM的占空比,然后控制led0pwmval的值从0变到 300,然后又从300变到0,如此循环,因此LED的亮度也会跟着从暗变到亮,然后又从亮变到暗。 下载与调试 程序编译无误后下载到实训平台,观察LED亮度的变化,并通过示波器观察输出的波形。 67 第十章 输入捕获实验 (实验 9) 1 实验目的 (1) 理解定时器的输入捕获功能; (2) 了解定时器的输入捕获功能测量方波周期或高电平宽度的原理。 2 实验任务 (1) 配置定时器通道IO口输入捕获模式; (2) 利用定时器的输入捕获功能测量方波周期或高电平宽度。 3 实验说明 输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32的定时器,除了TIM6和TIM7,其他定时器都有输入捕获功能。STM32的输入捕获,简单的说就是通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA等。 本实验使用TIM5_CH1来捕获高电平脉宽,也就是要先设置输入捕获为上升沿检测,记录发生上升沿的时候TIM5_CNT的值。然后配置捕获信号为下降沿捕获,当下降沿到来时,发生捕获,并记录此时的TIM5_CNT值。这样,前后两次TIM5_CNT之差,就是高电平的脉宽,同时TIM5的计数频率是已知的,从而可以计算出高电平脉宽的准确时间。 本实验目的是通过输入捕获,来获取TIM5_CH1(PA0)上面的高电平脉冲宽度,并从串口打印捕获结果。 4 预习要求 (1) 复习定时器中断方法。 (2) 了解输入捕获的工作过程。 (3) 输入捕获参数有哪些?分别有什么作用? 5 实验步骤 68 (1) 在实训平台上将PE5连接LED灯; (2) 复制上一个实验工程修改名称并保存为输入捕获实验; (3) 在PWM实验timer.c文件的基础上,添加两个函数:定时器通道输入捕获配置和定时器中断服务程序。 (4) 编写main()函数,程序编译正确; (5) 下载程序,打开串口调试助手观察捕获到的值。 硬件设计 图10.1 LED1与PE5连接 软件设计 (1) 编写timer.c文件。打开timer.c文件,在文件最下方接着编写。 a. 开启TIM5时钟和GPIOA时钟,配置PA0为下拉输出 要使用TIM5,必须先开启TIM5的时钟。这里还要配置PA0为下拉输出,因为要捕获TIM5_CH1上面的高电平脉宽,而TIM5_CH1是连接在PA0上面的,还有对GPIOA的初始化。 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能TIM5时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟 b. 初始化TIM5,设置TIM5的ARR和PSC 开启TIM5的时钟之后,需要设置ARR和PSC两个寄存器的值来设置输入捕获的自动重装载值和计数频率。库函数中是通过TIM_TimeBaseInit函数实现的。 c. 设置TIM5的输入比较参数,开启输入捕获 输入比较参数的设置包括映射关系、滤波、分频以及捕获方式等。这里需要设置通道1为输入模式,且IC映射到TI1(通道1)上面,并且不使用滤波(提高响 69 应速度)器,上升沿捕获。库函数是通过TIM_ICInit函数来初始化输入比较参数的: void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); d. 使能捕获和更新中断(设置TIM5的DIER寄存器) 因为要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准确。这两件事,都在中断里面进行,所以必须开启捕获中断和更新中断。 定时器的开中断函数TIM_ITConfig即可使能捕获和更新中断: TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断和捕获中断 e. 设置中断分组,编写中断服务函数 编写NVIC_Init()函数。然后,使能TIM5,就可以开始输入捕获。此时程序为: 图10.2 输入捕获配置 分组完成后, 还需要在中断函数里面完成数据处理和捕获设置等关键操作, 70 从而实现高电平脉宽统计。在中断服务函数里面,中断开始的时候要进行中断类型判断,在中断结束的时候要清除中断标志位。用到的函数为TIM_GetITStatus()函数和TIM_ClearITPendingBit(); 中断服务函数为: 图10.3 中断服务函数 通过以上步骤,定时器5的通道1就可以开始输入捕获了。 (2) 编写timer.h文件。在原来文件的基础上加入TIM5_Cap_Init的声明。 图10.4 timer.h文件 (3) 编写main()函数。main函数是在PWM实验的基础上修改来的,保留了 PWM输出,同时通过设置TIM5_Cap_Init(0XFFFF,72-1),将TIM5_CH1的捕获计数器设计为1us计数一次,并设置重装载值为最大,所以捕获时间精度为1us。 71 主函数通过TIM5CH1_CAPTURE_STA的第7位,来判断有没有成功捕获到一次高电平,如果成功捕获,则将高电平时间通过串口输出到电脑。 图10.5 主函数 下载与调试 程序编译无误后下载到实训平台,并与电脑通过串口连接,打开串口调试助手XCOM V2.0,长按WK_UP键松开,观察所捕获的数据(即高电平脉宽时间)。 72 第十一章 基于CAN总线的网络式超声波测距仪设计 (实 验 10) 1 实验目的 (1) 理解CAN通信工作原理; (2) 理解CAN通信程序、液晶显示程序。 2 实验任务 (1) 配置相关引脚的复用功能,使能和配置定时器外部输入捕捉中断; (2) 使用超声波模块测距; (3) 使用DS18B20数字温度传感器采集温度数据。 3 实验说明 (1) CAN是Controller Area Network的缩写(以下称为 CAN),是ISO国际标准化的串行通信协议。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986年德国电气商博世公司开发出面向汽车的CAN通信协议。此后,CAN通过ISO118及ISO11519进行了标准化,现在在欧洲已是汽车网络的标准协议。 CAN通信的相关寄存器及更全面的介绍请自行翻看STM32F1开发指南(精英版)—库函数版本,下面来看如何使用CAN进行通信。 CAN控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 (2) 超声波模块简介。本试验超声波模块采用HC-SR04模块。HC-SR04模块工作原理: a. 采用IO口TRIG触发测距,给至少10us的高电平信号; b. 模块自动发送8个40khz的方波,自动检测是否有信号返回; c. 有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2; 73 本模块使用方法简单,一个控制口发一个10us以上的高电平,就可以在接收口等待高电平输出。一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离。如此不断的周期测,即可以达到移动测量的值。 (3) DS18B20数字温度传感器简介。 DS18B20是由DALLAS半导体公司推出的一种的“一线总线”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。一线总线结构具有简洁且经济的特点,可轻松地组建传感器网络,从而为测量系统的构建引入全新概念,测量温度范围为-55~+125℃,精度为±0.5℃。现场温度直接以“一线总线”的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现9~l2位的数字值读数方式。它工作在3—5.5V的电压范围,采用多种封装形式,从而使系统设计灵活、方便,设定分辨率及设定的报警温度存储在EEPROM中,掉电后依然保存。其内部结构如图所示: 图11.1 DS18B20内部结构图 4 预习要求 (1) 了解超声波模块的工作原理; (2) 了解库函数中TFTLCD的常用功能函数; (3) 了解数字温度传感器的工作原理。 5 实验步骤 (1) 配置相关引脚的复用功能,使能和配置定时器外部输入捕捉中断; 74 (2) 编写CAN通信程序; (3) 编写液晶显示程序,显示测量距离; (4) 调用片内温度传感器采集的温度数据,对测量距离进行温度校正; (5) 编译无误,下载程序至实训平台并调试。 硬件设计 硬件资源占用比较多,CAN总线的发射和接收需要在两个芯片烧入各自的发射和接收程序,然后连接到总线进行通讯。见下图: 图11.2 超声波部分原理图(发送端) 图11.3 主控芯片CAN总线部分原理图(接收端) 用2根导线将两个模块CAN端子的CAN_L和CAN_L,CAN_H和CAN_H 75 连接起来。这里注意不要接反了(CAN_L接CAN_H),接反了会导致通讯异常。 将超声波模块接入板子,注意每个引脚对应的接口要一直,千万不要插反了,会导致模块损坏。 注意在通电检测时需要用USB1口进行供电。 软件设计 (1) 配置相关引脚的复用功能,使能CAN时钟。 使能CAN的时钟设置CAN的相关引脚为复用输出,这里需要设置PA11为上拉输入(CAN_RX 引脚)PA12为复用输出(CAN_TX 引脚),并使能PA口的时钟。使能CAN1时钟的函数是: RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟 (2) 设置CAN工作模式及波特率等。 在库函数中,提供了函数CAN_Init()用来初始化CAN的工作模式以及波特率,CAN_Init()函数体中,在初始化之前,会设置CAN_MCR寄存器的INRQ为 1让其进入初始化模式,然后初始化CAN_MCR寄存器和CRN_BTR寄存器之后,会设置CAN_MCR寄存器的INRQ为0让其退出初始化模式。所以在调用这个函数的前后不需要再进行初始化模式设置。下面来看看CAN_Init()函数的定义: uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct); 第一个参数就是CAN标号,这里的芯片只有一个CAN,所以就是CAN1。 第二个参数是CAN初始化结构体指针,结构体类型是CAN_InitTypeDef,下面来看看这个结构体的定义 76 图11.4 结构体 这个结构体看起来成员变量比较多,实际上参数可以分为两类。前面5个参数是用来设置寄存器CAN_BTR,用来设置模式以及波特率相关的参数,设置模式的参数是CAN_Mode,实验中用到常规模式 CAN_Mode_Normal。其他设置波特率相关的参数CAN_Prescaler,CAN_SJW,CAN_BS1和CAN_BS2分别用来设置波特率分频器,重新同步跳跃宽度以及时间段1和时间段2占用的时间单元数。后面6个成员变量用来设置寄存器CAN_MCR,也就是设置CAN通信相关的控制位。中文参考手册中对这两个寄存器的描述,非常详细。 初始化实例为: 图11.5 初始化 77 (3) 设置滤波器。 本实验中,将使用滤波器组0,并工作在32位标识符屏蔽位模式下。先设置CAN_FMR的FINIT 位,让过滤器组工作在初始化模式下,然后设置滤波器组0的工作模式以及标识符ID和屏蔽位。最后激活滤波器,并退出滤波器初始化模式。在库函数中,提供了函数CAN_FilterInit ()用来初始化CAN的滤波器相关参数,CAN_Init()函数体中,在初始化之前,会设置CAN_FMR 寄存器的INRQ为INIT让其进入初始化模式,然后初始化CAN滤波器相关的寄存器之后,会设置CAN_FMR寄存器的FINIT为0让其退出初始化模式。所以在调用这个函数的前后不需要再进行初始化模式设置。下面来看看CAN_FilterInit ()函数的定义: void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct); 这个函数只有一个入口参数就是CAN滤波器初始化结构体指针,结构体类型为 CAN_FilterInitTypeDef,下面看看类型定义: 图11.6 类型定义 结构体一共有9个成员变量,第1个至第4个是用来设置过滤器的32 id以及32位mask id,分别通过2个16位组合。第5个成员CAN_FilterFIFOAssignment用来设置FIFO和过滤器的关联关系,本实验是关联的过滤器0到FIFO0,值为CAN_Filter_FIFO0。第6个成员变量CAN_FilterNumber用来设置初始化的过滤器组,取值范围为0~13。第7个成员变量FilterMode用来设置过滤器组的模式,取值为标识符列表模式CAN_FilterMode_IdList和标识符屏蔽位模式CAN_Filter 78 Mode_IdMask。第8个成员变量FilterScale用来设置过滤器的位宽为2个16位CAN_FilterScale_16bit还是1个32位CAN_FilterScale_32bit。 第9个成员变量CAN_FilterActivation就很明了了,用来激活该过滤器。过滤器初始化参考实例代码: 图11.7 过滤器初始化 至此,CAN就可以开始正常工作了。 完整CAN通信初始化参考代码: Mode_IdMask 79 图11.8 CAN通信初始化 (4) 发送接受消息 在初始化CAN相关参数以及过滤器之后,接下来就是发送和接收消息了。 80 库函数中提供了发送和接受消息的函数。发送消息的函数是: uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage); 这个函数比较好理解,第一个参数是CAN标号,这里使用CAN1。第二个参数是相关消息结构体CanTxMsg指针类型,CanTxMsg结构体的成员变量用来设置标准标识符,扩展标示符,消息类型和消息帧长度等信息。 图11.9 发送消息 接受消息的函数是: void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage); 前面两个参数也比较好理解,CAN标号和FIFO号。第二个参数RxMessage是用来存放接受到的消息信息。结构体CanRxMsg和结构体CanTxMsg比较接近,分别用来定义发送消息和描述接受消息,可以对照着看一下,也比较好理解。 图11.10 接受消息 (5) CAN状态获取 对于CAN发送消息的状态,挂起消息数目等等之类的传输状态信息,库函 81 数提供了一些列的函数,包括CAN_TransmitStatus()函数,CAN_MessagePending()函数,CAN_GetFlagStatus()函数等等,可以根据需要来调用。 在can.c文件中只需要编写上述函数即可。 图11.11 can.c文件 至此,can通信模块已经全部处理好了,使用时在主函数中调用即可 (6) 下面进行超声波模块的编写。首先在HARDWARE文件下建立wave.c文件和wave.h文件。在wave.c中进行超声波模块的初始化函数以及测距函数的编写。 超声波初始化: 图11.12 超声波初始化 测距函数: 距离计算:TIM2_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数,1us计数一次,最大计数周期为65536us 82 图11.13 测距函数 以上语句的作用是得到超声波从发出到返回所用的时间。根据每个人设定的计时器的arr和psc不同,上面的计算方式也不同,目的是得到总的高电平时间。 (7) TFTLCD显示需要的相关设置步骤如下: 1) 设置STM32F1与TFTLCD模块相连接的IO。 这一步,先将TFTLCD模块相连的IO口进行初始化,以便驱动LCD,这里用到的是FSMC,FSMC在下边会有详细讲解。 2) 初始化TFTLCD模块。 这里没有硬复位LCD,因为实训平台的LCD接口,将TFTLCD的RST同STM32F1的RET1连接在一起了,只要按下开发板的RET1键,就会对LCD 进行硬复位。初始化序列,就是向LCD控制器写入一系列的设置值(比如伽马校准),这些初始化序列一般LCD供应商会提供给客户,直接使用这些序列即可,不需要深入研究。在初始化之后,LCD才可以正常使用。 3) 通过函数将字符和数字显示到TFTLCD模块上。 这一步则通过设置坐标—>写GRAM指令—>写GRAM来实现,但是这个步骤,只是一个点的处理,要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目的,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,就可以实现数字/字符的显示了。 注:请自行观看STM32开发手册(精英版)--库函数版本中第十八章有关FSMC的讲解,在此就不过多介绍。 重点函数使用: 83 //在指定位置显示一个字符 //x,y:起始坐标 //num:要显示的字符:\" \"--->\"~\" //size:字体大小 12/16/24 //mode:叠加方式(1)还是非叠加方式(0) void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode) //显示字符串 //x,y:起点坐标 //width,height:区域大小 //size:字体大小 //*p:字符串起始地址 void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p) //显示数字,高位为0,则不显示 //x,y :起点坐标 //len :数字的位数 //size:字体大小 //num:数值(0~4294967295); void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size) LCD模块配置步骤较为复杂,可以直接添加或者复制LCD初始化函数模板,这里不再提供。 (8) 与超声波一样,DS18B20的初始化编写同样可以在HARDWARE下建立ds18b20.c文件及ds18b20.h。 ds18b20.c文件程序如下: 84 85 图11.14 ds18b20.c文件 (9) 各模块具体操作步骤已经讲过,超声波的捕获由PA1出发,另设置PA0定时器2通道1为输入捕获模式,得到超声波返回的高电平时间,然后通过计算可以得到声音在空气中传播的时间;DS18B20通过PA8与MCU进行通信,可以得到温度值,然后通过计算得到声音在空气中传播的速度;这样便得到了距离。 测距公式:测试距离=(高电平时间*声速(340M/S))/2。 温度补偿公式:声速= 331.5+0.607*摄氏度 下面主要讲一下主函数中重要的语句。 86 在发送方:需要配置CAN通信发送模式以及超声波模块和DS18B20模块。 CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);//设置CAN通信为普通模式 Distance=temp/1000000* canbuf[1]*100*2; //得到距离 canbuf[2]=Distance; //保存在数组中 res=Can_Send_Msg(canbuf,3); //将数组canbuf[]中的数据通过Can总线传送过去 需要注意:CAN通信最大传送字节为8 图11.15 发送方程序 发送方程序样例:(由于各模块在前面简介中已经介绍过,这里只列出主函数例) 在接收方:需要配置CAN通信接受模式以及LCD显示数据。 87 图11.16 接收方程序 计时器程序编写步骤在前面已经讲过,这里不在赘述。 步骤总结: 发送端:首先将STM32ZET6程序模板改为STM32C8T6,具体操作如下 打开任意工程模板点击魔法棒按钮: 图11.17 点击魔法棒 之后弹出操作窗口: 88 图11.18 选择型号 按图上箭头指出选择STM32103C8接下来: 图11.19 添加Define 点击C/C++按钮,由于ZET6为大容量芯片,C8T6为中容量芯片,将上图箭头指向的“HD”改为“MD”点击OK即可开始编写程序,注意C8T6的各个管脚。 发送端代码编写: (1) CAN通信初始化正常模式 (2) 计时器初始化,选定预装载值和分频系数 (3) 超声波模块驱动编写,能够得到距离 (4) DS18b20驱动编写,能够得到温度值 (5) 主函数编写,通过CAN将得到的距离和温度值发送给接收显示端 接收端代码编写: 使用的是ZET6的芯片,注意工程建立不要弄混了。 (1) CAN通信初始化选择正常模式或者静默接受模式 (2) LCD初始化及显示页面设计 (3) 通过CAN接收距离和温度数据,存储在数组当中 (4) 用LCD显示接收到的数据 下载与调试 在代码编译成功之后,通过下载代码到实训平台上,通过LCD观察传输数据。并且改变超声波所测距离观察数据。 90 第十二章 伺服电机控制器设计(实验 11) 1 实验目的 (1) 掌握对输出脉冲的频率和个数的控制; (2) 实现对伺服电机梯形速度控制功能。 2 实验任务 (1) 运用位置控制指令函数设置加减速时间; (2) 调节伺服电机的启动频率和最高频率。 3 实验说明 (1) 伺服电机 伺服电机(ser motor)是指在伺服系统中控制机械元件运转的发动机,是一种补助马达间接变速装置。 伺服电机可使控制速度,位置精度非常准确,可以将电压信号转化为转矩和转速以驱动控制对象。伺服电机转子转速受输入信号控制,并能快速反应,在自动控制系统中,用作执行元件,且具有机电时间常数小、线性度高、始动电压等特性,可把所收到的电信号转换成电动机轴上的角位移或角速度输出。分为直流和交流伺服电动机两大类,其主要特点是,当信号电压为零时无自转现象,转速随着转矩的增加而匀速下降。 (2) 伺服驱动器 伺服驱动器(servo drives)又称为“伺服控制器”、“伺服放大器”,是用来控制伺服电机的一种控制器,其作用类似于变频器作用于普通交流马达,属于伺服系统的一部分,主要应用于高精度的定位系统。目前主流的伺服驱动器均采用数字信号处理器(DSP)作为控制核心,可以实现比较复杂的控制算法,实现数字化、网络化和智能化。功率器件普遍采用以智能功率模块(IPM)为核心设计的驱动电路,IPM内部集成了驱动电路,同时具有过电压、过电流、过热、欠压等故障检测保护电路,在主回路中还加入软启动电路,以减小启动过程对驱动器的冲击。功率驱动单元首先通过三相全桥整流电路对输入的三相电或者市电进行整流,得到相应的直流电。经过整流好的三相电或市电,再通过三相正弦PWM电压型逆 91 变器变频来驱动三相永磁式同步交流伺服电机。功率驱动单元的整个过程可以简单的说就是AC-DC-AC的过程。整流单元(AC-DC)主要的拓扑电路是三相全桥不控整流电路。 随着伺服系统的大规模应用,伺服驱动器使用、伺服驱动器调试、伺服驱动器维修都是伺服驱动器在当今比较重要的技术课题,越来越多工控技术服务商对伺服驱动器进行了技术深层次研究。 伺服驱动器是现代运动控制的重要组成部分,被广泛应用于工业机器人及数控加工中心等自动化设备中。尤其是应用于控制交流永磁同步电机的伺服驱动器已经成为国内外研究热点。当前交流伺服驱动器设计中普遍采用基于矢量控制的电流、速度、位置3闭环控制算法。该算法中速度闭环设计合理与否,对于整个伺服控制系统,特别是速度控制性能的发挥起到关键作用。 关于本实验所用伺服驱动器,以下是其参数选择说明。 伺服驱动器需要设置6个参数,分别为pr0.00*、pr0.01*、pr0.05*、0r0.06*、pr0.07*、r0.08*。 各指令设定的参数如下: Pr0.00 0 逆时针旋转为正方向 Pr0.01 0 位置控制模式 Pr0.05 0 光耦合器输入 Pr0.06 0 旋转方向设定 Pr0.07 3 pulse2控制速度,sign2控制方向 Pr0.08 10000 10000p/r 1) 驱动器最开始显示的数据为当前的转速;(图12.1) 图12.1 初始转速为0 图12.2 模式d01.SPd 2) 驱动器上电后按设置键S进入模式d01.SPd(图12.2),如再按一次将进入测试模式,显示当前的速度; 92 图12.3 模式PAr.000 图12.4 模式EE_SEt 3) 按模式键M进入参数设定模式PAr.000(图12.3),通过上、下、左键选择所要修改的参数,按S键进入该参数的设定值,上下左修改设定值,把对应参数的设定值修改后,再按住设置键约2秒后,界面自动返回到对应的参数设定模式PAr.***; 4) 在返回到对应的参数设定模式PAr.***后,按模式键M进入参数EEPROM写入模式EE_SEt;(图12.4) 图12.5 模式EEP- 图12.6 FiniSh 5) 按一次设置键S进入EEP-模式(图12.5),再按住向上键约5秒后,显示EEP---逐渐增加直到显示rESEt或FiniSh(图12.6)为止,设置参数写入完毕。断电后再次上电,参数才会生效。 4 预习要求 (1) 伺服电机控制器的各个引脚功能是什么? (2) 简单地了解伺服电机如何运行。 5 实验步骤 (1) 将24VPWM模块与已连接上交流伺服电机的交流驱动器相连; 93 (2) 将DAC上的GND24V和DGND连接,按键模块的KEY0和PE3相连; (3) 将24V电源和电源模块相连; (4) 复制工程模板,新建5个文件并写入函数,分别为main.c、 key.c、 key.h、 timer.c、timer.h; (5) 编译成功,下载程序至实训平台,并调试验证。 硬件设计 本实验用到的硬件资源有:24VPWM输出模块(原理图如图12.7所示)、交流伺服电机和交流伺服驱动器(如图12.8所示)。 (1) 24VPWM模块原理图如下图所示: 图12.7 24VPWM输出原理图 (2) 交流伺服电机与交流伺服驱动器如下图所示: 图12.8 交流伺服电机与交流伺服驱动器 94 (3) 伺服电机有三种控制模式:位置控制、速度控制、转矩控制。本实验选用第一种位置控制模式。 设定值 【0】 1 2 3 4 5 6 第1模式 位置 速度 转矩 位置 位置 速度 全闭环 图12.9 控制模式 第2模式 — — — 速度 转矩 转矩 — (4) 其指令脉冲输入有如下几个模式,这里使用脉冲串+符号控制的方式,PA2输出脉冲控制转速,PA3输出高电平控制正转,输出低电平控制反转。 图12.10 指令脉冲输入形态 (5) 电机的指令脉冲有三个可以配置的参数(图12.11),只需要设定第一项,电机每旋转一圈的指令脉冲数即可,这里选择为默认的10000p/r。配置完另外两个参数,其余选择默认参数即可。依据这些参数来配置交流伺服驱动器的参数。 (关于交流伺服驱动器的参数设定方法,请参看实验说明第4项) 95 图12.11 指令脉冲数设定 (6) 上面参数配置完后,开始讲解上位机与交流伺服控制器的连接。这里需要连接7个端口,3、4、5、6、7、29、41。3、5、7连接VCC24V,4连接PA2控制电机速度,6连接PA3控制电机方向,29为伺服电机开启输入端,低电平使能,所以要连接GND24V,41连接GND24V,端口连接如下图所示: 图12.12 端口连接 在实训平台上的连接(后面数字与接口一一对应),如图12.13所示。 96 图12.13 24VPWM输出模块 图12.14 连接GND24V和DGND (7) 在实训平台上操作时,有两个需要注意的地方。在使用时,图12.13中S5必须按下,S5连接着伺服驱动器的29端使能管脚,只有在29端为低电平时,电机才能开始工作,即S5为电机的工作开关。图12.14中的GND24V与DGND必须使用短线进行短接,否则电机不会运行。 软件设计 本次实验需要编写5个文件,分别为main.c, key.c, key.h, timer.c, timer.h。 (1) key.h和key.c的编写。 本实验中只需要用到一个按键来控制电机的开启与关断,所以只需配置一个端口即可,程序中使用的是PE3。 图12.15 key.h文件 图12.16 key.c文件 97 (2) timer.h和timer.c的编写。 脉冲输出采用两个定时器,定时器2输出高速脉冲,定时器3改变定时器2输出脉冲的频率。timer.h程序如下: 图12.17 timer.h文件 timer.c的程序如下: 图12.18 gpio_init()函数 freq1为当前输出的脉冲频率,初始值为设定的启动频率。dir为加减速的标志,因为freq1,dir在整个程序中一直在调用,所以需要定义为全局变量。gpio_init()函数是用来初始化输出端口PA.2和PA.3的。 a. 编写TIM2_PWM_Init()函数。它被用来配置高速脉冲,先使能定时器2的时钟,然后初始化TIM2,设置定时器的周期,预分频值,时钟分割系数,定时器的计数模式,然后按照结构体变量中的参数初始化定时器。再初始化定时器2的PWM输出模式,这里使用的是定时器2的CH3输出比较通道。定时器模式选择为PWM2模式,在定时器计数值小于比较值时,输出为无效电平,计数值大于比较值时,输出有效电平。然后使能比较输出,设置输出极性为高,即有效电平为高电平,无效电平为低电平。最后根据指定的参数初始化TIM2定时器的OC3,OC3指的是输出比较通道3。接着使能定时器2在CCR上的寄存器,使能TIM2的更新中断。 98 b. 配置中断。中断通道设置为定时器2中断,设置先占优先级(抢占优先级)为2级,从优先级(响应优先级)为3级,接着使能中断通道,并根据上面的参数初始化中断。最后设置一下定时器的比较值。 图12.19 TIM2_PWM_Init()函数 配置完定时器2后,开始配置定时器2的中断服务函数。 图12.20 TIM2_IRQHandler()函数 c. 编写TIM2_IRQHandler()函数。先检测TIM2是否发生了中断,如果发生了中断,则会执行提前设定好的程序(这里没设置其他程序),然后清除中断待处理位。没有发生中断则会重新等待进入中断。 d. 编写TIM3_Int_Init()函数。先使能定时器3的时钟,然后配置定时器计数周期,预分频值,时钟分割系数和计数模式,再使能定时器更新中断,配置定时 99 器中断通道,定时器中断通道选择TIM3中断,先占优先级为1级,高于定时器2,从优先级为3级,再打开中断通道,最后初始化中断。 图12.21 TIM3_Int_Init()函数 e. 编写TIM3_IRQHandler()定时器3的中断服务函数。这里用来改变定时器2输出脉冲的周期。先检查TIM3是否发生中断,如果发生了中断,则会清除中断 图12.22 TIM3_IRQHandler()函数 待处理位,再根据dir和freq1来判断加减速和是否关闭定时器。 f. 编写PWM_Change()函数。这里用来改变定时器2的计数周期。根据传入的频率值来计算计数周期,然后调用定时器2的初始化函数,改变定时器2的周期,然后开启TIM2。 图12.23 PWM_Change()函数 100 (4) 编写main.c文件。 先引用头文件delay.h, key.h, timer.h。这里电机转动指令脉冲数为10000p/r,即10000个脉冲转一圈,加速时间和减速时间都设置为3S,启动频率为2KHz,最高频率为62KHz,此时电机转速为372r/min。可以通过伺服驱动器的监视器查看转速。Extern u8 dir声明dir为外部变量。Key_Value为按键值,Key_Value的最高位为电机的当前运转状态,1代表电机处于开启状态,0代表电机处于关闭状态。Key_Value的低7位存储键值。gpio_init()为初始化PA2和PA3的函数,KEY_Init()初始化按键,中断分组为2,2位抢占优先级(0~3),2位响应优先级(0~3)。TIM2_PWM_Init()里的第一个参数为定时器周期,第二个为预分频系数,定时器周期计算公式为:时钟频率/{}频率*(预分频系数+1)}=定时器周期+1,当预分频系数为0时,不分频,时钟频率/频率=定时器周期+1。TIM3_Int_Init(),设为5ms中断一次,每隔5ms进入一次中断,改变定时器2的输出脉冲频率。在初始化时,把定时器全部关闭。 图12.24 main.c文件 接着在while内进行控制,KEY_Scan的返回值进行或运算是为了不影响Key_Value的其他位。Key_Value与0x03进行与运算,只取低三位来判断当前的键值。如果键值不为0,则进行后面的操作。当按键按下后,Key_Value右移7位,判断最高位的值,如果为0,则表示电机当前处于关闭状态,接着给电机加速脉冲,让电机加速到最高速度,并把Key_Value的最高位赋值为1(同时清除了键值),表示电机已开启。如果Key_Value的最高位为1,则表示当前电机处于运行状态,接着会打开定时器3,开始减速,并把Key_Value赋值为0。 101 图12.25 main.c文件 仿真与调试 程序编译无误使用软件仿真示波器观察脉冲频率,观察是否正确,再将程序下载至实训平台观察伺服电机是否正常运行。 102 第十三章 单相太阳能并网逆变器SPWM发生器设计 (实 验 12) 1 实验目的 (1) 学会使用高级控制定时器; (2) 理解SPWM波形的产生原理。 2 实验任务 (1) 对信号发生器的正弦信号进行频率和相位跟踪,调整SPWM波的频率、相位与其一致,模拟实现逆变并网; (2) 编写函数实现载波频率,正弦频率调节等功能的SPWM波输出控制指令。 3 实验说明 (1) TIM1和TIM8简介 高级控制定时器(TIM1和TIM8)由一个16位的自动装载计数器组成,它由一个可编程的预分频器驱动。它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM等)。使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节。高级控制定时器(TIM1和TIM8)和通用定时器(TIMx)是完全的,它们不共享任何资源,可以同步操作。 (2) TIM1和TIM8主要特性 16位向上、向下、向上/下自动装载计数器 ● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值 ● 多达4个通道: ─ 输入捕获 ─ 输出比较 ─ PWM生成(边缘或中间对齐模式) ─ 单脉冲模式输出 103 ● 死区时间可编程的互补输出 ● 使用外部信号控制定时器和定时器互联的同步电路 ● 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器 ● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态 ● 如下事件发生时产生中断/DMA: ─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发) ─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) ─ 输入捕获 ─ 输出比较 ─ 刹车信号输入 ● 支持针对定位的增量(正交)编码器和霍尔传感器电路 ● 触发输入作为外部时钟或者按周期的电流管理 (3) SPWM波形的产生原理 SPWM全称正弦脉冲宽度调制技术,是用一系列等幅不等宽的脉冲等效正弦波。SPWM技术是基于“面积相等,效用等效”原理,即形状不同的窄脉冲信号。 图13.1 SPWM波形的产生原理 4 预习要求 (1) 理解SPWM波形的产生原理; (2) 了解TIM1和TIM8主要特性。 5 实验步骤 (1) 使能定时器溢出中断和PWM输出功能; (2) 编写函数实现载波频率,正弦频率调节等功能的SPWM波输出控制指令; (3) 利用定时器的外部捕捉,实现对正弦信号频率和相位的采集; 104 (4) 编译无误后下载程序至实训平台并调试。 硬件设计 图13.2 电路原理图 软件设计 (1) 新建文件夹并命名为单相太阳能并网逆变器SPWM发生器设计,复制粘贴之前的工程模板文件,将工程文件改名为SPWM.uvprojx,同时修改timer.c和timer.h文件。 (2) timer.h文件的编写。 在这里声明Tim8的初始化函数:void TIM8_PWM_Init(u16 psc); 图13.3 timer.h文件 (3) timer.c文件的编写。 a. TIM8_PWM_Init(u16 psc)初始化 时钟使能:打开Tim8时钟、以及打开Tim8两个PWM输出互补通道CH1(PC.6)、CH1N(PA.7)所在的GPIO口时钟。如下图框内: 图13.4 时钟使能 b. SPWM通道CH1、CH1N I/O引脚初始化设置 PWM1输出通道CH1对应的是PC.6、互补通道CH1N对应的是PA.7。(除 105 此之外,以后如果用到了其他通道,在此处对其进行初始化即可) 图13.5 SPWM通道 c. Tim8定时器TIM_TimeBaseStructure初始化 与通用定时器初始化类似,我们在前面定义一个结构体。然后为结构体配置参数。这个结构体主要用来配置Tim8的计数功能。psc是Tim8初始化函数的入口参数。用来设置Tim8的计数频率。即72MHz/psc。TIM_Period:为多少次计数为一个周期。数值的选择与后来生成SPWM正弦表的精度有关。这里精度为10位,即1023。 图13.6 TIM_TimeBaseStructure初始化 d. TIM8通道模式配置 图13.7 TIM8通道模式配置 初始化完成后还要进行,预装载使能: TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable); //预装载使能 e. Tim8通道刹车 由于逆变器中有CMOS管,而高速的PWM驱动信号在达到功率元件的控制极时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。因此需要在之前的初始化程序中加入死区刹车配置。 106 图13.8 Tim8通道刹车 f. Tim8中断配置 对应的结构体的定义: 图13.9 Tim8中断配置 g. 这里还需要进行其他的操作: 图13.10 TIM8文件 到此初始化函数就写好了。 h. 正弦表的生成 在写Tim8中断服务函数之前,需要一组正弦数据。选择16进值数据,然后设置精度位。由于前面在初始化Tim8时,设置TIM_TimeBaseStructure.TIM _Period= 1023,即精度为10,因此这里就用10。接下来计算采样数:Tim8定时器频率为72Mhz/psc/1023。 107 图13.11 生成正弦表 i. Tim8捕获比较中断函数设置 void TIM8_CC_IRQHandler(void) 在写中断服务函数之前,首先定义两个全局变量代表访问正弦表数组状态。 图13.12 定义两个全局变量 到此就把将Tim8如何产生单路互补的SPWM波介绍完了。只需要将Tim8初始化函数放在主程序中调用,由于用到了中断,因此还应在主函数里设置NVIC中断分组。 108 (4) 编写主函数。 引用timer.h然后,定义变量psc,初值给到6。然后设置中断分组,然后调用Tim8初始化函数。 图13.13 设置中断分组 在while循环中添加一个灯的闪烁,用来表示程序的正常运行: 图13.14 while循环 (5) 程序下载与实验现象。 编译,待没有错误后,下载至实训平台。使用示波器采集PC.6和PA.7的信号。观察输出波形。 (6) 逆变器。 接下来,用生成的SPWM控制逆变扩展模块输出正弦波。原理:STM2ZET6产生两路互补的SPWM波:将这两个信号波传送给由IR2110构成的驱动板。驱动板产生两组共四路脉冲信号驱动单向全桥桥臂,控制CMOS管的关断与导通。CMOS的关断与导通的时间符合正弦变化,经过后面的RL滤波电路后就可以产生正弦波了。观察产生的正弦波,分析频率正弦波的频率与程序中产生的SPWM频率的关系。 109 程序执行STM32F103ZET6两个互补的SPWM12V电源供电IR2110驱动板两组四路触发信号单相全桥CMOS管产生正弦波供电 图13.15 逆变器的工作流程图 (9) SPWM波输出控制。 首先准备好之前写的关于按键的驱动。然后将它添加到工程里,并在主程序里调用。然后根据不同的键值编写不同的控制程序就可以了。 改变Tim8定时器的预分频值。在主函数中调用TIM8_PWM_Init(u16 psc)来初始化的Tim8。如果改变psc的值,再调用一次初始化函数,那么将改变Tim8定时器的预分频值。联想之前的公式就可以,改变我们的SPWM的频率,正弦波频率也将改变。而psc的改变可以通过按键控制,应当注意的是,psc应当是一个大于零的整数。 改变采样数。前面的程序中,正弦数表中,一共有240个数据。如果减少采样点数,这样输出一个周期正弦波所需的采样点数就会减少。一个采样点的时间是高低电平一共的时间,也就是定时器一个周期的计数。如果不重新初始化定时器,仅仅改变采样点数。那么,就可以实现在载波频率不变的情况下,改变正弦波的频率。 基于此思想,在main()函数所在的源文件中用extern 将之前的程序中的count1和num声明一下,就可以在主程序中使用这两个变量的值。灵活运用相关的知识,设计程序,使得改变后采样点数尽可能的均匀 110 附录一:带有温度补偿的超声波测距电路原理图 111 编后语 本实验指导书采用意法半导体公司生产的STM32MCU芯片及软件,以及微控制系统综合实训平台实验装置及软件。部分电路及软件由“电气工程学院-31335实验室”更新和修正。 实验程序已由“电气工程学院-31335实验室”实践验证。 电气工程学院 郑 维 王 威 2019-07 112
因篇幅问题不能全部显示,请点此查看更多更全内容