【嵌入式基础】定时器PWM练习

article/2025/10/5 10:35:22

本次实验是在前面实验的基础上加入定时器和PWM脉冲宽度调制,之前的延时功能都是通过delay函数实现的。而本次作业通过定时器Timer方式实现时间的精准控制,更有利于CPU的运行,再通过PWM脉冲宽度调制,输出波形,分析PWM的占空比随时间变化。

目录

一、定时器Timer简介

1、定时器Timer介绍

2、定时器Timer分类

 3、定时器功能单元

4、定时器特色

5、定时器事件

6、定时器时钟源

 7、工作原理

二、定时器实现LED闪烁

1、题目要求

2、建立工程并设置工程环境

3、代码编写

4、编译烧录

 5、效果展示

三、基于PWM的呼吸灯

1、题目要求

2、PWM脉冲宽度调制

3、STM32上的PWM

3、建立工程和配置环境

 4、代码编写

5、编译烧录

四、实验总结

五、参考文献


一、定时器Timer简介

1、定时器Timer介绍

在大容量的 STM32F103xx增强型系列产品包含最多2个高级控制定时器、4个普通定时器和2个基本定时器,以及2个看门狗定时器和1个系统嘀嗒定时器,本次的目标芯片(STM32F103C8T6)也是大容量系列,所以具备以上所述的定时器种类。

  • 能够对内部时钟信号或外部输入信号进行计数,数值达到设定要求时,向CPU发起中断请求,完成外部程序的运行
  • 本质就是进行计数,选择内部时钟脉冲,作为计数器时,技术信号的来源选择非周期脉冲信号

2、定时器Timer分类

首先学习一下分类也可以大致了解一下各个定时器的特点,此处按功能分类,可分为如下三类:

基本定时器:几乎没有任何对外输入/输出,常用作时基,实现基本的计数、定时功能。例如Systick,TIM6,TIM7(其中Systick又为核内定时器,TIM6,TIM7为外设定时器),框图如下

 通用定时器:除了基本定时器的时基功能外,还可对外做输入捕捉、输出比较以及连接其它传感器接口【编码器和霍尔传感器】 。(例如TIM2-TIM5)框图如下

 高级定时器: 此类定时器的功能最为强大,除了具备通用定时器的功能外,还包含一些与电机控制和数字电源应用相关的功能,比方带死区控制的互补信号输出、紧急刹车关断输入控制。(例如TIM1,TIM8)框图如下

 3、定时器功能单元

可把定时器大致分为六个功能单元

从模式控制单元:负责时钟源、触发信号源的选择;控制计数器的启停、复位、门控等;
时基单元:定时器核心单元。负责时钟源的分频、计数、溢出重装等。
输入单元:为部分的时钟信号、 捕捉信号、 触发信号提供信号源。
比较输出单元:通过对比较寄存器与计数器的数值匹配比较,实现不同输出波形。
触发输出单元:输出触发信号给到其它定时器或外设。
捕捉比较单元: 是输入捕捉或比较输出的公共执行单元。

4、定时器特色

定时器中的PSC/ARR/RCR/CCRx寄存器具有预装载功能,即每类寄存器具有双寄存器机制,分别由各自的影子寄存器和预装载寄存器组成。
TIMx_PSC 分频寄存器,设置分频器对时钟源的分频比或分频系数
TIMx_CNT 核心计数器,对从分频器过来的时钟进行计数
TIMx_ARR 自动重装寄存器,为计数器设置计数边界或初始值,决定计数脉冲的多少或计时周期长短。比如计数器向上计数时,记到多少发生溢出;向下计数时从多少开始往下计数。带预装载使能控制位ARPE@TIMx_CR1
TIMx_RCR 重复计数器,重复计数器是个向下计数器,当计数器发生 TIMx_RCR+1次溢出动作后会触发更新操作。
TIMx_CCR 捕捉/比较寄存器,带预装载使能控制位OCxPE@TIMx_CCMR
当关闭预装载使能位时,用户修改预装寄存器的数据后会立即被拷贝进影子寄存器【实际寄存器】 ,否则,修改过的预装寄存器的数据只能等到下次更新事件来完成从预装寄存器数据到影子寄存器的拷贝更新。
影子寄存器是真正起作用的寄存器,预装载寄存器为影子寄存器提供缓冲,提前做数据或指令准备; 因为定时器工作往往具有一定周期性,如果每次我们的参数修改都直接作用于实际寄存器,往往不可避免会影响到当前周期的正常计数以及相关的输出动作。
用户操作的永远只是预装载寄存器!包括DMA的访问。
ARR/CCR影子寄存器的预装功能可软件开启或关闭。PSC/RCR影子寄存器的预装功能不可通过软件关闭,始终开启。在开启预装载功能时,影子寄存器的内容必须借助更新事件完成更新!

5、定时器事件

以下几类事件都可触发中断或DMA请求;

更新事件: 比如影子寄存器更新往往需借助该事件;
触发事件:定时器收到各类触发输入信号时往往激发该事件;
捕获、比较事件: 发生输入捕捉或比较输出时会产生该事件;
由第三章的定时器特色学习我们知道影子寄存器的更新依赖于更新事件,我们这变介绍一下更新事件。首先我们必须学习一下更新操作。
注意:更新操作与更新操作之间的牵连
更新操作【事件源】可分为以下三类:

1.核心计数器的溢出【上溢或下溢】
2.软件复位操作【对TIMX_EGR@UG】 (软件产生更新操作,软件置1,硬件自动清零)
3.工作在复位模式下的定时器收到触发信号【即复位触发信号】
 

6、定时器时钟源

四个来源;内部时钟源、 ETR脚、触发信号,编码器模式

  • 1、内部时钟源, 来自芯片时钟系统给到定时器的时钟
  • 2、来自于芯片ETR脚的外来时钟
  • 3、来自于各类触发输入信号作为时钟
  • 4、编码器模式

 框图如下

 7、工作原理

在选定的时钟源(可以是内部的也可以是外部的)和预分频器TIMX_PSC的驱动下,根据设置的计数模式(向上、向下、中央对齐)自动。

装载计数器TIMX_CNT开始计数;如果使能了相应的事件(更新事件、触发事件、输入捕获、输出比较)则会产生相应的中断。

如果没有开启输入和输出,只使能了计数器计数溢出后自动装载,可以做为一个简单定时器使用,计数器自己开始周期计数
如果开启了通道输入捕获,当检测到ICx信号上相应的边沿后,计数器(CNT)的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中,通过中断的方式可以读取出来假设为n1,然后更改输入捕获的信号级性(上升沿或下降沿),当再次检测到ICx信号上相应的边沿后,计数器(CNT)的当前值再次被锁存到捕获/比较寄存器(TIMx_CCRx)中假设为n2;n2 -n1节可算出电平的持续时间
如果开启了输出控制,可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的PWM信号。
如果选择外部的同步时钟信号(TI1F_ED、TI1FP1、TI2FP2)作为计数器的时钟源,可以用来统计脉冲,实现脉冲频率采集功能。

二、定时器实现LED闪烁

1、题目要求

前面的实验中,延时功能都是通过delay函数实现的。这种方式,相当于让CPU一直在做无用功,还不能做别的事情。这一节通过定时器的方式实现时间的精准控制,相当于给CPU上了一个闹钟,CPU平时处理其它任务,当定时时间到了以后,处理定时相关的任务。请设置一个5秒的定时器,每隔5秒从串口发送“hello windows!”;同时设置一个2秒的定时器,让LED等周期性地闪烁。

2、建立工程并设置工程环境

新建工程,进行基本配置。通过点击“ACCESS TO MCU SELECTOR”来创建一个新的工程:

 芯片选择“STM32F103C8”,开启工程。

 打开外部时钟,点击“System Core”,选择RCC,在右侧弹出的菜单栏中选择“Crystal/Ceramic Resonator”。

 选择调试接口,点击“System Core”,选择SYS。,在右侧弹出的菜单栏中选择“Serial Wire”。

 配置IO。配置PC15,并命名为D1。这里我们只使用一个LED。

配置定时器2与定时器3。我们使用定时器来实现定时的功能。如图所示,点击“Timer”,选择“TIM2”和“TIM3”,配置定时器的时钟源为内部时钟;设置分频系数为71,向上计数模式,计数周期为5000,使能自动重载模式。

 分频系数那里虽然写的是71,但系统处理的时候会自动加上1,所以实际进行的是72分频。由于时钟我们一般会配置为72MHZ,所以72分频后得到1MHZ的时钟。1MHZ的时钟,计数5000次,得到时间5000/1000000=0.005秒。也就是每隔0.005秒定时器2会产生一次定时中断。

配置中断,开启定时器2和3的中断。

并且生成定时器2中断优先级配置代码

选择Connectivity,点开USART1,Mode选择异步通信Asynchronous:

时钟配置。这里,我们需要把它倍频到72MHZ。先点击“Clock Configuration”页面,按照下面红色框中的值,从左到右进行配置即可。

 生成工程。在工程管理页面“Project Manager”,先点击“Project ”,选择如下配置:

  再点击“Code Generator”,进行如下配置:

 最后,点击右上角那个不像按钮的按钮“GENERATE CODE”,即可生成相应工程。工程生成之后,会弹出对话框,提示你是否需要打开。选择打开。

3、代码编写

修改工程。生成工程后,打开,添加中断响应之后所需的一些代码。在main.c文件中添加如下内容:

	HAL_TIM_Base_Start_IT(&htim2);HAL_TIM_Base_Start_IT(&htim3);

该函数表示启动相应的定时器,“h”表示HAL库,“tim2”表示定时器2,“tim3”表示定时器3,所以这行代码的意思就是启动定时器2和定时器3。

 串口输出代码:

	uint8_t hello[20]="hello windows!\r\n";

 接下来添加的函数为定时器的中断回调函数,当产生定时中断的时候,会自动调用这个函数。在函数内部定义了一个静态变量:time_cnt。当它大于等于100的时候,才会执行if里面的代码。也就是说需要发生100次中断,才会让LED的状态翻转。前面已经算过了,一次定时中断的时间是0.005秒,所以400次中断的时间是0.005*400=2.0秒。也就是说每隔2.0秒,LED的状态翻转一次。

代码如下:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{static uint32_t time_cnt =0;static uint32_t time_cnt3 =0;if(htim->Instance == TIM2){if(++time_cnt >= 400){time_cnt =0;HAL_GPIO_TogglePin(D1_GPIO_Port,D1_Pin);}}if(htim->Instance == TIM3){if(++time_cnt3 >= 1000){time_cnt3 =0;HAL_UART_Transmit(&huart1,hello,20,100000);}}
}

4、编译烧录

 编译上面修改完之后的代码

结果显示,编译无误,开始烧录。

借助flymcu软件进行烧录

 5、效果展示

串口通信结果

通过串口接收时间来看,是每隔5s发送一个“hello world”,符合实验要求。

LED闪烁 

也符合实验预期。 

三、基于PWM的呼吸灯

1、题目要求

 使用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动你外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。

2、PWM脉冲宽度调制

使用脉冲占空比拟合不同波形的方式称为 PWM(脉冲宽度调制)控制技术——通过 对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。PWM 控制 的基本原理为:冲量相等而开头不同的窄脉冲加在具有惯性的环节上时,其效果基本 相同。其中冲量指窄脉冲的面积;效果相同指环节输出响应波形基本相同。 例如:可以用一系列等幅不用一系列等幅不等宽的脉冲来代替一个正弦半波,见图

要改变等效输出正弦波幅值,按同一比例改变各脉冲宽度即可。 若把拟合的波形改成呼吸特性曲线,即可得到控制呼吸灯使用的 PWM 波形,要生成 拟合的 PWM波形,通常使用计算法和调制法,本文中使用计算法:根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形。在下边编程实现中会详细说明。

要改变PWM输出波形的宽度,就要改变比较寄存器 CCRx 的值,想要输出不通宽度来拟合正弦波,则需要CCRx的值呈现如下图的变化趋势,即要生成一张CCRx的数值表,按周期变化将表中元素的值赋给CCRx。

3、STM32上的PWM

PWM产生
STM32的定时器除了TIM6和7,其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。

PWM相关寄存器
包含三个寄存器:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。设置TIMx_CCMRx寄存器OCxPE位以使能相应的预装载寄存器,最后还要设置TIMx_CR1寄存器的ARPE位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。在TIMx_CCMRx寄存器中的OCxM位写入110(PWM模式1)或111(PWM模式2),能够独立地设置每个OCx输出通道产生一路PWM。
捕获/比较模式寄存器(TIMx_CCMRx)
下图为TIMx_CCMR1寄存器的各位描述:

3、建立工程和配置环境

 新建工程,进行基本配置。通过点击“ACCESS TO MCU SELECTOR”来创建一个新的工程:

 芯片选择“STM32F103C8”,开启工程。

 打开外部时钟,点击“System Core”,选择RCC,在右侧弹出的菜单栏中选择“Crystal/Ceramic Resonator”。

 选择调试接口,点击“System Core”,选择SYS。,在右侧弹出的菜单栏中选择“Serial Wire”。

配置定时器3和定时器4。如图,选中定时器3和定时器4;选择时钟源为“Internal Clock”,通道2选择“PWM Generation CH2”,设置分频系数为71,计数周期为500,其它默认。设置占空比初始值为10,其实这里不写也没影响。

 

 时钟配置。这里,我们需要把它倍频到72MHZ。先点击“Clock Configuration”页面,按照下面红色框中的值,从左到右进行配置即可。

生成工程。在工程管理页面“Project Manager”,先点击“Project ”,选择如下配置:

再点击“Code Generator”,进行如下配置:

 最后,点击右上角那个不像按钮的按钮“GENERATE CODE”,即可生成相应工程。工程生成之后,会弹出对话框,提示你是否需要打开。选择打开。

 

 4、代码编写

修改工程。打开工程,主要修改main.c文件,定义一个变量,用来存储占空比:

初值设为0.

uint16_t duty_num = 0;

 开始TIM3和TIM4的通道2,输出PWM。代码:

HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_2);

 while函数中调用函数,代码如下:

while (duty_num< 500){duty_num++;__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, duty_num);  __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, duty_num);  			HAL_Delay(1);}while (duty_num){duty_num--;__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, duty_num);    __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_2, duty_num);  HAL_Delay(1);}HAL_Delay(200);

5、编译烧录

编译函数:

编译无误,可以进行烧录,借用flymcu软件烧录 :

效果展示

 

四、实验总结

通过这次实例演训,初步认识了STM32定时器的相关理论知识,以及在STM32F103C8T6核心开发板下,通过使用定时器Timer方式,来实现串口定时输出及LED灯周期闪烁的操作步骤。还学习到了PWM的相关理论知识,以及在STM32F103C8T6核心开发板下,通过TIM3和TIM4输出PWM波形实现2个 LED呼吸灯效果的操作步骤;

虽然中间过程有点曲折,但最终实验结果还是基本能符合预期。

五、参考文献

http://www.mcublog.cn/stm32/2021_01/stm32cubemx-dingshiqi-led/
http://www.mcublog.cn/stm32/2021_01/stm32cubemx-pwm-huxideng/
https://blog.csdn.net/qq_39257301/article/details/100057561
https://blog.csdn.net/zmhDD/article/details/111942507
https://blog.csdn.net/qq_45237293/article/details/111997424

 


http://chatgpt.dhexx.cn/article/CyPbhaGl.shtml

相关文章

STM32—sg90编程

什么是舵机&#xff1f; 如下图所示&#xff0c;最便宜的舵机sg90&#xff0c;常用三根或者四根接线&#xff0c;黄色为PWM信号控制 用处&#xff1a;垃圾桶项目开盖用、智能小车的全比例转向、摄像头云台、机械臂等 常见的有0-90、0-180、0-360 怎么控制舵机 向黄色信号线“…

【电机控制算法】SVPWM算法的应用(基于Simulink与STM32外设场景的仿真)

文章目录 前言一、扇区判断二、各扇区基础矢量作用时间的计算1.若处于扇区I2.若处于扇区II3.若处于扇区III4.若处于扇区IV5.若处于扇区V6.若处于扇区VI7.In a word 三、STM32的外设场景1. TIMx的计数器CNT2. TIMx的输入捕获寄存器CCRx3. TIMx的两种PWM模式 四、互补输出且呈中心…

STM32-定时器中断实验

一、通用定时器基本原理 1-1 三种定时器区别 1.三种STM32定时器区别 定时器种类位数计数器模式产生DMA请求捕获/比较通道互补输出特殊应用场景高级定时器&#xff08;TIM1,TIM8&#xff09;16向上&#xff0c;向下&#xff0c;向上/下可以4有带死区控制盒紧急刹车&#xff0…

CRC校验

一、CRC原理。 CRC校验的原理非常简单&#xff0c;如下图所示。 其中&#xff0c;生成多项式是利用抽象代数的一些规则推导出来的&#xff0c;而模2加&#xff08;也就是异或&#xff09;&#xff0c;是对应于有限域的除法。 二、CRC算法。 那么在FPGA当中&#xff0c;也有好…

CRC-16

文章目录 A.1 CRC16 算法A.1.1 CRC16 算法参数设置A.1.2 LengthA.1.3 CounterA.1.4 Data IDA.1.5 CRCA.1.6 CRC16 算法示例A.1.7 CRC16 算法推荐(查表法)A.1.8 CRC16 实例(查表法) A.1 CRC16 算法 A.1.1 CRC16 算法参数设置 CRC16 算法中要求了 Counter、Data ID、CRC 等参数…

CCR

不用任何与创建线程、资源互斥有关系的API写多线程程序 这次的例子&#xff0c;是一个很简单的控制台&#xff0c;她将面对瞬间提交的百万的数据&#xff0c;而面不改色&#xff08;CPU、内存非常平稳&#xff09;&#xff0c;队列中始终只保存最新的数据&#xff0c;每次只处理…

CRC16

CRC选择 当数据帧长度在8bits-128bits范围内时&#xff0c;推荐CRC-8(CRC-8能够减少额外比特的开销&#xff0c;且有更好的性能表现) 当数据帧长度在128bits-2048bits范围内时&#xff0c;推荐CRC-12&#xff0c;CRC-16&#xff0c;CRC-CCITT(CRC-12额外比特的开销更小&#x…

stm32cubemx HAL库之定时器

定时器原理简单说明&#xff1a; 如上图所示&#xff0c;定时器存在3个寄存器&#xff0c;分别是&#xff1a; 计数器寄存器&#xff08;CNT&#xff09; 预分频器寄存器&#xff08;PSC&#xff09; 自动重载寄存器&#xff08;ARR&#xff09; 预分频寄存器的输入频率是时钟…

CRC详解

CRC-知识解析 cyclic redundancy check 写在前面的话&#xff1a; 之前在做学校项目的时候用到了CRC 原理&#xff0c;但在网上查找的过程中&#xff0c;发现讲解CRC知识的资源很多&#xff0c;但是对新手比较友好的、讲的十分清楚的又很少&#xff0c;很多博主也不求甚解&am…

CCR(Condition Code Register:条件代码寄存器)的作用

CCR是一个显示执行指令后的结果和处理器的状态的8位寄存器。根据微型计算机的不同&#xff0c;名称也会不同&#xff0c;但是所有的微型计算机都有。在大多数微型计算机的情况下&#xff0c;用户不能直接读写&#xff0c;但有些微型计算机可以读写。您可以通过执行可以测试CCR位…

STM32定时器的预装寄存器及影子寄存器PSC—ARR-CCRx

在谈预装寄存器及影子寄存器的差别前&#xff0c;不妨先对STM32定时器的时基单元做个基本了解。STM32各系列的定时器结构和框架基本是一样的&#xff0c;时基单元也一样。 下面时基单元是以STM32F3系列为参考。 时基单元中的TIMx_PSC、 TIM_ARR两个寄存器加上捕捉比较模块中TIM…

输出比较功能中的pwm以及其他功能的区分

首先我们要知道的是pwm是输出比较的子集 PWM模式下&#xff1a; ARR 决定输出频率 &#xff0c;CCR决定输出占空比 输出比较模式下&#xff1a; ARR 决定输出频率 CCRx 决定每个通道的初始相位。 一般使用输出比较都是想要去输出一个频率可变的pwm信号&#xff0c;那怎么通过…

STM32 PWM输出

STM32 PWM输出 工作过程&#xff1a; 我们假定定时器工作在向上计数PWM 模式&#xff0c;且当 CNT<CCRx 时&#xff0c;输出 0&#xff0c;当 CNT>CCRx 时输出1。那么就可以得到如上的PWM 示意图&#xff1a; 当 CNT 值小于 CCRx 的时候&#xff0c;IO 输出低电平(0)&a…

简单明了的说明STM32的PWM原理以及实现方法

申明以下都是个人理解&#xff0c;仅供参考。如果错误欢迎指教。本文不讲底层&#xff0c;根据实际使用来逆向讲解。 1.什么是pwm&#xff1f; pwm最简单的理解就是“功率”&#xff0c;调节PWM的占空比就是调节功率。 2.如何调节占空比&#xff1f; 图1 根据图1很容易看出…

CCRX寄存器

TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);// TIMx_CCRx寄存器能够在任何时候通过软件进行更新以控制输出波形&#xff0c;条件是未使用预装载寄存器(OCxPE’0’&#xff0c;否则TIMx_CCRx影子寄存器只能在发生下一次更新事件时被更新)。这里设置为Disable 就是为了…

JavaScript权威指南 第13章 异步JavaScript

JavaScript权威指南 第13章 异步JavaScript 13章 异步JavaScript13.1 使用回调的异步编程13.1.1 定时器13.1.2 事件13.1.3 网络事件13.1.4 Node中的回调与事件 13.2 期约13.2.1 使用期约使用期约处理错误 13.2.2 期约链13.2.2 解决期约13.2.4 再谈期约和错误catch和finally方法…

javascript权威指南(第四版)

Java Script是一种功能强大的基于对象的脚本语言。Java Script程序可以直接嵌入HTML页面。与Web浏览器定义的文档对象模型(DOM)一起使用时&#xff0c;JavaScript可以创建动态HTML(DHTML)内容&#xff0c;允许用户与客户端的Web应用程序交互。 JavaScript语法以流行的程序设计语…

《JavaScript权威指南》学习笔记(一)

跟着《JavaScript权威指南》整理的一些知识点和自己的小拓展。有不足之处请指正。 1、try catch 防止程序异常直接报错退出&#xff0c;而是能对异常进行一些处理&#xff0c;具体处理就在catch中。最好是在最外层函数使用。 2、HTML不区分大小写、XHTML区分大小写、JavaScri…

《javascript权威指南》精读笔记-持续更新

《javascript权威指南》 作用域链 表达式 原始表达式 对象和数组的初始化表达式 函数定义表达式 函数直接量 属性访问表达式 调用表达式 对象创建表达式 运算符 表达式计算 var function for in with debugger use strict 对象 创建对象 属性的查询和设置 作为关联数组的对象 继…

JavaScript权威指南(第6版)

JavaScript权威指南&#xff08;第6版&#xff09; JavaScript权威指南 第6版&#xff08;影印版&#xff09;上册 Beginning iOS Programming, 2014年 Gradle for Android (2016年3月 Finished)