转载请注明出处:http://blog.csdn.net/alvin_jack/article/details/70665462
文章均出自个人理解,错误之处敬请指出;
前言
上篇1.1Drv/Character Device Drv(字符型驱动设备)的叙述方式感觉没有逻辑性,只是从框架的角度进行了阐述,这篇换个思路,从调用顺序上来描述PWM驱动设备的实现原理;
基础知识
在ARM单片机中,PWM实际实现机制就是通过TIMER定时器来实现的,不管是调频还是脉宽,核心的思想就是通过一个计数器和两个寄存器(计算频率/调节脉宽),我之前遇到过有把PWM单独拿出来作为外设模块的MCU(例如飞思卡尔的),也有归类到定时器下的(例如意法半导体的),不管怎么算,核心的思想是不会变的,所以这次就从意法的mcu出发来叙述Nuttx下的PWM驱动的实现原理。
启动流程
Nuttx的启动就不再详细叙述,有空再写篇关于Nuttx启动详细讲解文章,这里从PWM的入口驱动开始讲解,也就是在config/stm32f103-minimum/src/stm32_appinit.c文件中board_app_initialize()函数;
board_app_initialize-- #板载应用初始化
|
v
stm32_bringup()------ #逐个启动板载应用
|
v
stm32_pwm_setup()---- #执行pwm初始化
|
v
stm32_pwminitialize(STM32F103MINIMUM_PWMTIMER)--- #初始化指定的TIMx
pwm_register("/dev/pwm0", pwm)--- #注册PWM驱动到内核文件按系统
.....
#初始化其他板载应用
.....
其实咋一看流程非常清晰,几乎和写单片机程序没什么两样,但是越是看起来简单的东西实际实现起来就越不简单;
stm32_pwminitialize(STM32F103MINIMUM_PWMTIMER)
Nuttx中的驱动是类linux的,和上一篇讲到的串口一样也是需要一些特定的文件结构(我们尝试着以字符型驱动去理解分析试试);
在开始之前首先分析下层级关系,在PWM的驱动中也存在和串口(uart_dev_s、uart_ops_s)类似的数据结构(stm32_pwmtimer_s、pwm_ops_s),说明Nuttx的套路是一样的,我们的估计没有错,对上的接口都是*_dev_s和*_ops_s两种数据接口;现在先分析流程,后面来分析数据结构;
stm32_pwminitialize(int timer)-- #stm32pwm初始化,形参为指定定时器通道(硬件层面)
|
v
stm32_pwmtimer_s *lower = &g_pwm3dev; #此处的g_pwm3dev,就是定义的*_dev_s(硬件有多少就定义多少)
|
v
pwm_register("/dev/pwm0", pwm); #将获得的*_dev_s注册到系统(pwm就是g_pwm3dev)
好像和上面的有点重复 --!,这里主要是要分析stm32_pwmtimer_s、pwm_ops_s、pwm_lowerhalf_s三个数据结构,好像和uart的驱动有点区别了;
stm32_pwmtimer_s
这个结构体主要是描述驱动的主要模块,板卡的所有PWM驱动模块都是被封装成这样的结构体变量的;所处的路径是:
nuttx/arch/arm/src/stm32/stm32_pwm.c中,这个文件貌是是所有stm32公用的(不然怎么这么多定时器);
static struct stm32_pwmtimer_s g_pwm1dev;
static struct stm32_pwmtimer_s g_pwm2dev;
...
static struct stm32_pwmtimer_s g_pwm16dev;
static struct stm32_pwmtimer_s g_pwm17dev;
static struct stm32_pwmtimer_s g_pwm1dev =
{
.ops = &g_pwmops, #ops结构,老熟人了
.timid = 1, #用的硬件Timer1就填1
.channels = #通道数据结构
{
#ifdef CONFIG_STM32_TIM1_CHANNEL1
{
.channel = 1, #用的通道1就填1
.pincfg = PWM_TIM1_CH1CFG, #映射实际的GPIO
.mode = CONFIG_STM32_TIM1_CH1MODE, #PWM通道的工作模式(频率/脉宽)
},
#endif
...#这里还有很多个通道,分析结构就先省略掉
.timtype = TIMTYPE_TIM1, #定时器的工作模式(向上计数/向下计数...)
.mode = CONFIG_STM32_TIM1_MODE,
#ifdef CONFIG_PWM_PULSECOUNT
.irq = STM32_IRQ_TIM1UP, #计数中断
#endif
.base = STM32_TIM1_BASE, #stm32定时器的基地址,也是老熟人了
.pclk = TIMCLK_TIM1, #stm32定时器的时钟分频系数
};
其实这么一看,和uart的dev比起来,pwm简单太多了;
pwm_ops_s
这个数据结构应该也是用来操作timer寄存器组的结构体了,
所处的路径是:
nuttx/arch/arm/src/stm32/stm32_pwm.c中,这个结构体也是多个dev公用的,全文也只是定义了一个;
static const struct pwm_ops_s g_pwmops =
{
.setup = pwm_setup, #pwm初始设置
.shutdown = pwm_shutdown, #
.start = pwm_start, #
.stop = pwm_stop, #
.ioctl = pwm_ioctl, #
};
static int pwm_setup(FAR struct pwm_lowerhalf_s *dev);
static int pwm_shutdown(FAR struct pwm_lowerhalf_s *dev);
#ifdef CONFIG_PWM_PULSECOUNT
static int pwm_start(FAR struct pwm_lowerhalf_s *dev,
FAR const struct pwm_info_s *info,
FAR void *handle);
#else
static int pwm_start(FAR struct pwm_lowerhalf_s *dev,
FAR const struct pwm_info_s *info);
#endif
static int pwm_stop(FAR struct pwm_lowerhalf_s *dev);
static int pwm_ioctl(FAR struct pwm_lowerhalf_s *dev,
int cmd, unsigned long arg);
毫无疑问,ops都会使用到dev的数据结构,但是pwm这里面偏偏要搞一个pwm_lowerhalf_s这个数据结构,不知道干啥的,但会儿收拾ta;简单来说ops就是之前用来写裸机配置单片机外设那一套,只是把所有配置参数都封装在了中,配置方法封装在了ops中;所以具体的方法就不分析了,具体问题具体解决吧;
pwm_lowerhalf_s
所处的路径:nuttx/include/nuttx/drivers/pwm.h
/* This structure is a set a callback functions used to call from the upper-
* half, generic PWM driver into lower-half, platform-specific logic that
* supports the low-level timer outputs.
*/
struct pwm_lowerhalf_s;
说出来你可能不信,这货是个空的结构体,大概表达的意思是说,上半部分调用的回调函数可以通过这个结构进入PWM驱动器的下半部分,平台专用逻辑,支持低电平定时器输出。按这么说,就是个傀儡呗...为了验证这一说法,我们截取一段ops的代码来佐证:
static int pwm_start(FAR struct pwm_lowerhalf_s *dev,
FAR const struct pwm_info_s *info)
{
int ret = OK;
FAR struct stm32_pwmtimer_s *priv = (FAR struct stm32_pwmtimer_s *)dev;
...
...
...
return ret;
}
明显可以看出来,在ops实际使用的情况,是强行转换为了stm32_pwmtimer_s,也就是说pwm_lowerhalf_s是为整个os驱动的一致性而设立的变量,目的是让驱动结构的一致性和可读性更强。