nuttx入门-点亮LED

Nuttx是一种实时操作系统,被用在PX4飞控上。国内关于nuttx的介绍很少,所以写这篇博客和大家分享,并且记录自己的学习历程。

关于Nuttx的配置方法在 https://www.youtube.com/channel/UC0QciIlcUnjJkL5yJJBmluw 。最开始的两篇视频里有,大家请先把这个视频的前五篇看一遍。否则真的一点看不懂我接下来要说的。

我这里从第三篇视频也就是第一个例程中开始讲开来。视频中用的是stm32C8T6,可是我手头只有stm32VET6的开发板,而且LED的引脚不同。所以为了亮灯,我们需要修改LED引脚定义。

这个视频有一点不好的地方是喜欢用makefile menuconfig,这种傻瓜式操作很方便,但是其实不利于代码的理解。视频中进入Application然后选择LED Example,所以我们去apps里面找到examples下的leds_main。打开文件过后在文件的底端发现了貌似主函数的leds_main,这个函数的作用就是创建一个线程,也就是那句task_create。然后创建的task的代码是led_daemon,这个线程的代码在文件的上方。虽然很多参数不知道什么用处的,但是闪烁灯的逻辑无非是亮灯等待一定时间然后灭灯。所以我们可以把循环的部分扣出来毛估估一下,猜个大概

for (; ; )
    {
      userled_set_t newset;
      userled_set_t tmp;

//等待一定时间,通过递增到一个数字,亮灯,然后再从这个数字递减到0,灭灯
      if (incrementing)
        {
          tmp = ledset;
          do
            {
              tmp++;
              newset = tmp & supported;
            }
          while (newset == ledset);

          /* REVISIT: There are flaws in this logic.  It would not work
           * correctly if there were spaces in the supported mask.
           */

          if (newset == 0)
            {
              incrementing = false;
              continue;
            }
        }
      else
        {
          /* REVISIT: There are flaws in this logic.  It would not work
           * correctly if there were spaces in the supported mask.
           */

          if (ledset == 0)
            {
              incrementing = true;
              continue;
            }

          tmp = ledset;
          do
            {
              tmp--;
              newset = tmp & supported;
            }
          while (newset == ledset);
        }

      ledset = newset;
      printf("led_daemon: LED set 0x%02x\n", (unsigned int)ledset);
      //设置亮灯还是灭灯
      ret = ioctl(fd, ULEDIOC_SETALL, ledset);
      if (ret < 0)
        {
          int errcode = errno;
          printf("led_daemon: ERROR: ioctl(ULEDIOC_SUPPORTED) failed: %d\n",
                 errcode);
          goto errout_with_fd;
        }

      usleep(500*1000L);
    }

我原本以为前面创建的userled_set_t会是具体指向某个led,但是这条思路无果而终,这时我看到了ioctl,我一开始以为是打印点东西,后来注意到上面有printf了,不会这么矛盾吧,然后百度一下才发现有门道,这时linux用用户空间到设备驱动的接口,虽然nuttx不是linux系统,但是还是借鉴了posix规范。第一个参数是指向设备,第二个参数是命令,第三个参数是前面那个命令的参数。

而我们需要知道指向设备的的参数具体指向哪里,所以找到fd赋值的地方

fd = open(CONFIG_EXAMPLES_LEDS_DEVPATH, O_WRONLY);其中重要的参数是CONFIG_EXAMPLES_LEDS_DEVPATH

怎么做呢?当然是全局搜索关键词,我全局搜索的范围包括了git上面pull下来的三个工程(apps,nuttx,tools)。但是诡异的是搜索到另一个文件samv71-xult.h里面有定义这个宏定义之外没有别的地方用了这个宏定义。samv71-xult.h明显是另一个芯片的代码,虽然看里面的内容我们知道在这个芯片,CONFIG_EXAMPLES_LEDS_DEVPATH会指向/dev/userleds,但是我还是想找到在哪定义了这个宏定义。我突然想到之前在看makefile的时候,看到很多宏定义其实是拆开来定义的,什么意思?就是,CONFIG_EXAMPLES_LEDS_DEVPATH的定义方式是比如CONFIG_EXAMPLES_ + LEDS_DEVPATH。这么做的好处就是格式可以统一,而且可以一个个扫描地去定义,如果是SPI的例程,我估计就是CONFIG_EXAMPLES_ + SPI_DEVPATH。所以在这里我全局搜索了LEDS_DEVPATH。然后在apps/examples/leds/kconfig里面找到了

config EXAMPLES_LEDS_DEVPATH
	string "LED device path"
	default "/dev/userleds"

看来我上面猜错了不是LEDS_DEVPATH而是EXAMPLES_LEDS_DEVPATH是可变的。但是我还是找到了我想要的。

我不知道为什么在samv71-xult.h里面会再次定义这个变量,但是kconfig是在我们使用make menuconfig的时候被调用的,所以我比较确信是在这里定义了LED的设备定义。所以我们需要去找/dev/userleds。

找这个文件,我的第一反应是这应该是一个文件夹下面对应的一个文件,所以我在文件夹里面搜索了一下,没找到userleds这个文件,然后又搜索了一下dev,也没找到这个文件夹。然后只能在代码中全局搜索了一下,发现在NuttxPortingGuide.html里面有解释,Nuttx的文件系统是个伪文件系统,而且和一般的文件系统不一样,一般文件系统是伪文件去对应真实文件,但是nuttx里面是真实文件去对应伪文件,我也不知道这个是啥意思,但能明确的是按照目录名字去找/dev/userleds是找不通的。

查了大半天,结果什么都没有发现,自然是有点气馁。但是NuttxPortingGuide.html确实是个好东西,之前用VSCode看,没展开成网页形式,全是代码,就无视掉了,用网页打开后就舒服很多了。目录里面专门有一个led的模块,里面讲了这不是默认模块,所以不会写在核心驱动里面,但是还是按照核心驱动的模板写了这么一个模块。它有两个重要的头文件。一个是/include/board.h,另一个是include/nuttx/board.h,我在这里把路径给写全了分别是,\nuttx\configs\stm32f103-minimum\include\board.h 和\nuttx\include\nuttx\board.h。前者写了一堆非核心驱动的模块宏定义,后者申明了一堆非核心驱动模块的函数。那具体实现函数在哪呢?具体实现函数肯定根据板子走的,那我们还是需要去具体板子的目录下面去找。

我们一开始会使用./configure.sh stm32minimun/userleds来设置环境变量,所以找到这个文件夹,发现这个文件夹下面只有一个defconfig,打开这个文件搜索,只有一个跟LED相关的,就是CONFIG_ARCH_LEDS这个变量。然后在Makefile里面找到这个变量的用途

ifeq ($(CONFIG_ARCH_LEDS),y)
  CSRCS += stm32_autoleds.c
else
  CSRCS += stm32_userleds.c
endif

CSRCS经过大致看了下别的文件的使用方式,发现应该是C_SRCS的意思,就是具体.c文件的意思。在defconfig里面写了CONFIG_ARCH_LEDS is not set,所以明显是使用了stm32_userleds.c这个文件。然后打开这个文件发现这里面具体实现了上面在\nuttx\include\nuttx\board.h定义过的一堆函数。然后其中stm32_gpiowrite是不是很熟悉,然后这个函数的参数是GPIO_LED1,所以我就去找这个参数的具体定义,在include的一堆文件里面,我觉得定义最可能在stm32f103_minimum.h里面,打开后发现,我们梦寐以求的变量就在这里,我使用的板子的灯在B0口,所以改成

#define GPIO_LED1         (GPIO_OUTPUT|GPIO_CNF_OUTPP|GPIO_MODE_50MHz|\
                           GPIO_OUTPUT_CLEAR|GPIO_PORTB|GPIO_PIN0)

然后编译一下,烧进板子里。然后按照最开始提到的视频里面做一遍,然后就让灯闪烁起来了。

总结一下,大致猜测一下nuttx的流程。首先使用configure.sh stm32f103-minimum/userleds来指明这是个stm32f103-minimum最小系统,然后userleds里面包含了为了闪灯所需要定义的全局变量。然后make将\nuttx\include\nuttx\board.h作为头文件,stm32f103-minimum/src文件夹里面的文件作为c文件,完成灯的闪烁代码的编译。编译完过后,会被当成一个设备,放在dev下面,等待被配置。

你可能感兴趣的:(Nuttx学习)