jklincn


Stm32F103 + NuttX(四):自定义板级配置与应用


从本章起,系列文章都会配套开源代码

仓库链接:https://github.com/jklincn/nuttxspace

本章代码:https://github.com/jklincn/nuttxspace/tree/series/ch04


由于我手头不仅有一个开发板,还配备了一个 LCD 屏幕,以及自己购买了一个温湿度传感器以及一个 WIFI 模块,因此一个自定义的板级配置还是比较重要的,便于后续的开发。

新建板级配置

本文通过借鉴 stm32f103-minimum 的配置,自己写一个树外的板级配置文件夹,名字就叫做 atk-dnf103-v2 吧。这里需要先创建一个总的配置文件夹 myboard,然后再创建特定板的文件夹,最后的目录结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
lin@future:~/nuttxspace$ ls
apps  myboard  new_all  nuttx  old_all  stm32f103-minimum
lin@future:~/nuttxspace$ tree myboard/
myboard/
└── atk-dnf103-v2
    ├── CMakeLists.txt
    ├── configs
    │   └── nsh
    │       └── defconfig
    ├── include
    │   └── board.h
    ├── Kconfig
    ├── scripts
    │   └── ld.script
    └── src
        ├── atk-dnf103-v2.h
        ├── CMakeLists.txt
        ├── stm32_appinit.c
        ├── stm32_boot.c
        └── stm32_bringup.c

对比使用 myboard/atk-dnf103-v2/configs/nsh/defconfig 生成的与使用 stm32f103-minimum:nsh 生成的 .config 文件,只改动了部分关键配置(生成调试信息、芯片型号、板级型号、是否有 LED 和 按钮),其余配置保留不变。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
lin@future:~/nuttxspace$ diff old new
74c74,75
< # CONFIG_DEBUG_SYMBOLS is not set
---
> CONFIG_DEBUG_SYMBOLS=y
> CONFIG_DEBUG_SYMBOLS_LEVEL="-g"
264c265
< CONFIG_ARCH_CHIP_STM32F103C8=y
---
> # CONFIG_ARCH_CHIP_STM32F103C8 is not set
276c277
< # CONFIG_ARCH_CHIP_STM32F103ZE is not set
---
> CONFIG_ARCH_CHIP_STM32F103ZE=y
415c416
< CONFIG_STM32_MEDIUMDENSITY=y
---
> CONFIG_STM32_HIGHDENSITY=y
421a423
> CONFIG_STM32_HAVE_FSMC=y
435a438
> CONFIG_STM32_HAVE_DAC1=y
453a457,458
> # CONFIG_STM32_DAC1 is not set
> # CONFIG_STM32_FSMC is not set
598c603
< CONFIG_RAM_SIZE=20480
---
> CONFIG_RAM_SIZE=65536
607,610c612,623
< # CONFIG_ARCH_BOARD_STM32_TINY is not set
< CONFIG_ARCH_BOARD_STM32F103_MINIMUM=y
< # CONFIG_ARCH_BOARD_CUSTOM is not set
< CONFIG_ARCH_BOARD="stm32f103-minimum"
---
> # CONFIG_ARCH_BOARD_STM3210E_EVAL is not set
> CONFIG_ARCH_BOARD_CUSTOM=y
> 
> #
> # Custom Board Configuration
> #
> CONFIG_ARCH_BOARD_CUSTOM_NAME="atk-dnf103-v2"
> CONFIG_ARCH_BOARD_CUSTOM_DIR="../myboard/atk-dnf103-v2"
> CONFIG_ARCH_BOARD_CUSTOM_DIR_RELPATH=y
> # CONFIG_BOARD_CUSTOM_LEDS is not set
> # CONFIG_BOARD_CUSTOM_BUTTONS is not set
> # end of Custom Board Configuration
615,620d627
< CONFIG_ARCH_HAVE_LEDS=y
< CONFIG_ARCH_LEDS=y
< # CONFIG_ARCH_LEDS_CPU_ACTIVITY is not set
< CONFIG_ARCH_HAVE_BUTTONS=y
< # CONFIG_ARCH_BUTTONS is not set
< CONFIG_ARCH_HAVE_IRQBUTTONS=y
625,627d631
< CONFIG_STM32F103MINIMUM_BLUEPILL=y
< # CONFIG_STM32F103MINIMUM_BLACKPILL is not set
< # CONFIG_STM32F103MINIMUM_FLASH is not set

这里不给具体的代码了,src 文件夹中所有的函数都可以是空实现,因为我们只做一个串口输出,这在内核初始化时会自动配置。主要是以下几个函数需要定义一下。

  • board_app_initialize
  • stm32_boardinitialize
  • stm32_bringup

重新配置一下

cmake -B build -DBOARD_CONFIG=../myboard/atk-dnf103-v2/configs/nsh -GNinja

编译烧录 ctrl+shift+B,切换回 Windows 串口界面,板子自动复位输出 NuttShell (NSH) NuttX-12.12.0,且一切正常,这说明当前配置已经可以正常运行 NuttX。

这里有个坑是不能依赖 menuconfig 的 Save minimal config,它输出的最小配置因为 Kconfig 配置原因无法在 out-of-tree 情况下加入 ARCH 等配置,需要手动添加。

但看起来 cmake --build build -t savedefconfig 来生成最小配置是正常的。

自己编写 APP

我们之前的例程都是用 nuttx-apps 中的内容,即官方为我们准备的 APP。本节会开始自己编写一些在 nuttx 上运行的用户应用程序。

参考:Custom Apps How-to — NuttX latest documentation,但官网的教程用的是 makefile,本文使用的是 cmake,因此这个教程只能略微参考一下。

我们还是希望用 Outside of the Main Source Trees 的方式添加,以保持目录结构清晰。

创建 hello 应用

在根目录下创建 myapps 文件夹,链接到 apps 中。

mkdir myapps
cd apps/
ln -s ../myapps/ myapps

链接(看上去又不像是 oot 了)的作用有两个,

  1. 可以直接在 apps/CMakeLists.txt 中添加目录
  2. 可以让 nuttx_generate_kconfig() 自动生成 kconfig

先修改 apps/CMakeLists.txt,在 add_subdirectory(wireless) 后添加

add_subdirectory(myapps)

让整个 apps 构建系统知道我们的自定义应用。

然后在 myapps 下创建一个 CMakeLists.txt,其作用是包含了子目录以及在 menuconfig 中生成一个 My Apps 的菜单,用于我们选择要编译进固件的应用。

1
2
nuttx_add_subdirectory()
nuttx_generate_kconfig(MENUDESC "My Apps")

再创建 hello 文件夹,在其中创建

  1. Kconfig:定义一个 MY_APPS_HELLO Config,用来选择是否编译 hello 应用。

    config MY_APPS_HELLO
            tristate "Hello"
            default n
            ---help---
                    Enable the My Hello App
    
  2. CMakeLists.txt:这里用 CONFIG_MY_APPS_HELLO 包裹 nuttx_add_application,具体原因可以看下文的《坑:CMake 目标重复定义》

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    if(CONFIG_MY_APPS_HELLO)
      nuttx_add_application(
        NAME
        my_hello
        SRCS
        hello.c
        STACKSIZE
        2048
        PRIORITY
        100)
    endif()
    
  3. hello.c:写个最简单的输出

    1
    2
    3
    4
    5
    6
    7
    
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
      printf("Hello, world!\n");
      return 0;
    }
    

总体的目录如下:

lin@future:~/nuttxspace$ tree myapps/
myapps/
├── CMakeLists.txt
└── hello
    ├── CMakeLists.txt
    ├── hello.c
    └── Kconfig

手动加入 hello 应用

删除原有配置

cd nuttx
rm -rf build

先进行配置

cmake -B build -DBOARD_CONFIG=../myboard/atk-dnf103-v2/configs/nsh -GNinja

进入配置菜单

cmake --build build -t menuconfig

选择 Application Configuration -> My Apps -> Hello,空格选中,然后按 Q 退出,按 Y 选择保存退出。

可以在 nuttx/build/.config 中看到

#
# My Apps
#
CONFIG_MY_APPS_HELLO=y
# end of My Apps

之后就是编译、烧录、运行,不再重复,最后放一张截图

如果你想每一次配置后,hello 都自动勾选中,可以从以下方法二选一

  1. myapps/hello/Kconfig 中将 MY_APPS_HELLO 的 default 改成 y

  2. myboard/atk-dnf103-v2/configs/nsh/defconfig 中加入

    CONFIG_MY_APPS_HELLO=y
    

坑:CMake 目标重复定义

起初写完后进行配置会报重复定义的错:

CMake Error at cmake/nuttx_add_application.cmake:155 (add_library):
  add_library cannot create target "apps_my_hello" because another target
  with the same name already exists.  The existing target is a static library
  created in source directory "/home/lin/nuttxspace/myapps/hello".  See
  documentation for policy CMP0002 for more details.
Call Stack (most recent call first):
  /home/lin/nuttxspace/myapps/hello/CMakeLists.txt:28 (nuttx_add_application)

排查后发现,NuttX 的 CMake 会将 apps 目录添加两次。

第一次在 245 行,用于扫描所有应用并生成 Kconfig 菜单。在这个阶段,项目的 .config 配置还没有被加载到 CMake 中,因此所有的 CONFIG 变量此时都是未定义的,因此官方的 apps 此时都不会被添加。

1
2
3
4
5
# Add apps/ to the build (if present)

if(NOT EXISTS ${NUTTX_APPS_BINDIR}/Kconfig)
  add_subdirectory(${NUTTX_APPS_DIR} preapps)
endif()

第二次在 631 行,用于真正的 apps 添加。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Add apps/ to the build (if present)

if(NOT CONFIG_BUILD_KERNEL)

  if(EXISTS ${NUTTX_APPS_DIR}/CMakeLists.txt)
    add_subdirectory(${NUTTX_APPS_DIR} apps)
  else()
    message(
      STATUS "Application directory not found at ${NUTTX_APPS_DIR}, skipping")
  endif()

endif()

因此在自定义的 app 中,需要采取方法来解决重复定义问题,这里采取和官方例程一样的做法,使用 CONFIG 来保护

1
2
3
if(CONFIG_MY_APPS_HELLO)
  ...
endif()

加入 LED

目前的代码是最简单的,没有使用任何外设资源,只使用了 USART1 来做串口,下一步的想法是逐步加入更多的板级支持。

首先加一个 LED,这个之前我们做过,因此这边简单写一下。

修改板级配置

src/CMakeLists.txt 中加入 LED 支持,宏 CONFIG_USERLED 表示让用户自己管理 LED,而不是交给内核自动管理(对应宏 CONFIG_ARCH_LEDS)。

1
2
3
if(CONFIG_USERLED)
  list(APPEND SRCS stm32_userleds.c)
endif()

board.h 中加入 LED 的数量

1
#define BOARD_NLEDS        2

atk-dnf103-v2.h 中定义两个 LED

1
2
3
4
5
#define GPIO_LED0         (GPIO_OUTPUT|GPIO_CNF_OUTPP|GPIO_MODE_50MHz|\
                           GPIO_OUTPUT_SET|GPIO_PORTB|GPIO_PIN5)

#define GPIO_LED1         (GPIO_OUTPUT|GPIO_CNF_OUTPP|GPIO_MODE_50MHz|\
                           GPIO_OUTPUT_SET|GPIO_PORTE|GPIO_PIN5)

stm32_userleds.c 中修改 g_ledcfg 数组与板级 LED 接口函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static const uint32_t g_ledcfg[BOARD_NLEDS] =
{
  GPIO_LED0,
  GPIO_LED1,
};

void board_userled(int led, bool ledon)
{
  if ((unsigned)led < BOARD_NLEDS)
    {
      stm32_gpiowrite(g_ledcfg[led], !ledon);
    }
}

void board_userled_all(uint32_t ledset)
{
  int i;

  for (i = 0; i < BOARD_NLEDS; i++)
    {
      bool on = (ledset & (1 << i)) != 0;
      stm32_gpiowrite(g_ledcfg[i], !on);
    }
}

stm32_bringup.c 中加入用户 LED 初始化代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#ifdef CONFIG_USERLED
#  include <nuttx/leds/userled.h>
#endif

int stm32_bringup(void) {
  int ret = OK;
#ifdef CONFIG_USERLED
  /* Register the LED driver */

  ret = userled_lower_initialize("/dev/userleds");
  if (ret < 0)
    {
      syslog(LOG_ERR, "ERROR: userled_lower_initialize() failed: %d\n", ret);
    }
#endif
  return ret;
}

启用 LED 支持

在配置菜单中启用板级自定义 LED 控制,

  • Board Selection -> Custom Board Configuration -> Custom board LEDs (选中)
  • Board Selection -> Custom Board Configuration -> Board LED Status support(取消选中)
  • Device Drivers -> LED Support -> LED driver(选中)
  • Device Drivers -> LED Support -> Generic Lower Half LED Driver(选中)\

和原配置对比下来,主要是启用了 4 个配置。

CONFIG_BOARD_CUSTOM_LEDS=y
CONFIG_ARCH_HAVE_LEDS=y
CONFIG_USERLED=y
CONFIG_USERLED_LOWER=y

重新下板(指代配置、编译、烧录,后面的文章都会使用下板来代替这三步)后,可以在 /dev 下看到设备节点已经被创建了。

NuttShell (NSH) NuttX-12.12.0
nsh> ls /dev
/dev:
 console
 ttyS0
 userleds
nsh> 

创建 led 应用

由于我们刚刚已经学会了怎么创建自定义应用,因此我们这节就不使用原来的 apps/examples/leds 了,直接自己手写一个更简单一点的。

在 myapps 目录下创建 led 文件夹,分别创建以下文件

Kconfig

config MY_APPS_LED
        tristate "LED"
        default n
        ---help---
                Enable the My LED App

CMakeLists.txt

if(CONFIG_MY_APPS_LED)
  nuttx_add_application(
    NAME
    led
    SRCS
    led.c
    STACKSIZE
    2048
    PRIORITY
    100)
endif()

led.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <nuttx/config.h>

#include <sys/ioctl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include <nuttx/leds/userled.h>

#define LED_DEVPATH "/dev/userleds"

int main(int argc, FAR char *argv[])
{
  int fd;
  int ret;
  userled_set_t supported;
  userled_set_t current_state;
  userled_set_t new_state;

  /* Open the LED driver */

  fd = open(LED_DEVPATH, O_RDWR);
  if (fd < 0)
    {
      int errcode = errno;
      printf("ERROR: Failed to open %s: %d\n", LED_DEVPATH, errcode);
      return EXIT_FAILURE;
    }

  /* Get the set of LEDs supported */

  ret = ioctl(fd, ULEDIOC_SUPPORTED, (unsigned long)((uintptr_t)&supported));
  if (ret < 0)
    {
      int errcode = errno;
      printf("ERROR: ioctl(ULEDIOC_SUPPORTED) failed: %d\n", errcode);
      close(fd);
      return EXIT_FAILURE;
    }

  /* Get the current state of the LEDs */
  
  ret = ioctl(fd, ULEDIOC_GETALL, (unsigned long)((uintptr_t)&current_state));
  if (ret < 0)
    {
       printf("Could not read current state.\n");
       close(fd);
       return EXIT_FAILURE;
    }

  /* Toggle logic:
   * If any supported LED is ON, turn ALL supported LEDs OFF.
   * If ALL supported LEDs are OFF, turn ALL supported LEDs ON.
   */

  if ((current_state & supported) != 0)
    {
      /* At least one is on, turn all off */
      new_state = 0;
      printf("Turning LEDs OFF\n");
    }
  else
    {
      /* All are off, turn all supported on */
      new_state = supported;
      printf("Turning LEDs ON\n");
    }

  /* Apply the new state */

  ret = ioctl(fd, ULEDIOC_SETALL, new_state);
  if (ret < 0)
    {
      int errcode = errno;
      printf("ERROR: ioctl(ULEDIOC_SETALL) failed: %d\n", errcode);
      close(fd);
      return EXIT_FAILURE;
    }

  close(fd);
  return EXIT_SUCCESS;
}

编写完代码后,还需要和之前一样在配置菜单中手动启用 LED 应用,最后上板测试

nsh> 
NuttShell (NSH) NuttX-12.12.0
nsh> ?
help usage:  help [-v] [<cmd>]

    [          cp         false      mkdir      sleep      umount     
    ?          echo       help       mount      test       xd         
    cat        exec       hexdump    pwd        true       
    cd         exit       ls         rm         uname      

Builtin Apps:
    nsh    sh     led    
nsh> led
Turning LEDs ON
nsh> led
Turning LEDs OFF

保存成新配置

最后,为了以后复现方便,这里用 cmake --build build -t savedefconfig 把 LED 的配置做了导出,保存到 myboard/atk-dnf103-v2/configs/led/defconfig。它是 nsh 的增量配置,后续就可以直接配置而无需重复修改 menuconfig。


本站不记录浏览量,但如果您觉得本内容有帮助,请点个小红心,让我知道您的喜欢。