Nuttx系统学习笔记(三)——使用Nuttx操作STM32F429外设

2023-05-16

在上一篇,我们已经学会了如何将Nuttx进行烧录,以及学会了如何部署这个操作系统,接下来我们就要使用这个操作系统来实现我们对嵌入式设备的控制,当然也是从点灯开始的。这个基于Posix架构的操作系统使用起来是跟FreeRTOS那些操作系统是有区别的,所以首先我先补充一下这个操作系统的一些需要注意的地方:

目录

0x01 关于这个系统的一些补充

0x02 底层驱动解读

(一)GPIO.h

0x03 Nuttx操作系统的驱动编写

(一)character-index

(二)block-index

(三)special-index

(四)注册驱动函数以及解除注册驱动函数

0x04 编写LED驱动

(一)思路

(二)驱动框架搭建

0x05 使用已经封装好的驱动来点灯

(一)修改库文件


0x01 关于这个系统的一些补充

去研读这个操作系统的文档可以发现这个操作系统有两种内核构建模式,一种是Kernel构建,一种是flat构建,当然这两种构建模式下所存在的内存管理制度是不一样的,内核构建与Linux更贴切,它具有内存管理单元(MMU),虽然说它最后是会映射在物理内存上,但是它可以使用虚拟内存这个东西运行比实际内存更大的东西,并且只有在内核模式下才可以执行进程这个概念,当然,在内核构建模式下,在进程结束后,它会回收该进程的资源以及空间,所以你可以不需要free来释放内存,也不会导致内存泄漏。但是对于flat构建,它是在偏上内存之外的未受保护的平面地址空间中运行的,flat构建是没有MMU来进行构建的,进程退出时并没有自动清除的功能,所以要手动free,否则会导致内存泄漏。

如果使用内核构建模式,则需要MMU来进行管理,与Linux构建的方式类似。对于ARM的内存管理对于虚拟地址的内存分区(4GIB),一共分为3GIB用户空间以及1GIB的内核空间分区。对于这4GIB的内存分区,以及一级页表将其分为4096个条目,每个条目1MByte;二级页表将这1MByte再细分成256个条目,每个条目4KByte ,这个4K也是系统中最小的页面,这样可以提高内存的使用效率。

在低端的嵌入式设备(cortex-m)里程序直接运行在物理内存上。在高端的嵌入式设备(cortex-A)里最终操控的也是物理内存,但是由于有MMU这个硬件,所以有了虚拟内存这个概念,MMU实现了虚拟内存到物理内存的映射关系。

那么如何配置这些内存模式呢,可以通过设置下面的宏来定义:

  • CONFIG_PAGING(按需分页,也是一种虚拟内存的管理方法,需要MMU)

  • CONFIG_BUILD_PROTECTED(保护构建,不需要使用MMU来支持保护版本,地址空间为平面,不会进行地址映射)

  • CONFIG_ARCH_ADDRENV(启动每个程序时,都会为新任务创建一个新的地址环境,需要MMU)*

  • CONFIG_BUILD_KERNEL(类似于Linux,可以使用进程,具有内核区域以及用户区域)

来使其进入不同的模式,其中CONFIG_BUILD_KERNEL=y是使得Nuttx中支持使用进程的必要条件,只有内核构建才支持进程模式。

这个操作系统的教程入口:https://www.youtube.com/channel/UC0QciIlcUnjJkL5yJJBmluw

 

0x02 底层驱动解读

在这里我是使用的STM32F429IGT6的开发板,当然这个开发板是在它的支持list中是没有的,所以我选择了mikroe-stm32f4:nsh来进行配置,可以使用如下指令快速查找:

./tools/configure.sh -L | less | grep "stm32f4"

之后可以得到下面这些开发板型号,可以看到下面这些配置:

 这里只有几块开发板可以选择:stm32f411e-discomikroe-stm32f4mikroe-stm32f4。之后就去网上查资料了,看看哪一块跟我这块开发板上的芯片是一样的,最后决定使用stm32f429i-disco试一下了:

./tools/configure.sh -l  stm32f429i-disco:nsh
make menuconfig

在菜单中进行如下配置,当然这些都是那块stm32f429i-disco上的东西,我们需要到底层改一下底层驱动编号,或者直接使用gpio操作也是可以的。
  

 

打开后保存一下,然后再make,然后打开codeblocks IDE,把我们nuttx系统中生成的的头文件以及源文件包含进去,为什么这么干呢,因为这样可视化程度比较高,没别的意思。。

毕竟不是自己手头上这块开发板的型号,也不是这些外设,所以我们需要看懂它的底层驱动文件,然后在文件中改他的引脚号即可。所以我们是直接操作gpio,而不是直接使用它的examples的例程,当然也可以花时间去改,但是还是走最近的路线吧:

 

(一)GPIO.h

在nuttx的库中找到gpio.h的库,找到之后打开研究一下这个文件,最后发现这是GPIO的一些底层实现的驱动文件,在里面我们可以看到在这里定义了GPIO的几种类型:

#define GPIOC_WRITE      _GPIOC(1)
#define GPIOC_READ       _GPIOC(2)
#define GPIOC_PINTYPE    _GPIOC(3)
#define GPIOC_REGISTER   _GPIOC(4)
#define GPIOC_UNREGISTER _GPIOC(5)
#define GPIOC_SETPINTYPE _GPIOC(6)
  • GPIOC_WRITE:这个其实在单片机中,我们可以看做是GPIO电平的输出,可以给0和1,返回值:0=output a low value; 1=output a high value

  • GPIOC_READ:这个在单片机中就是输入的状态,读取GPIO引脚的电平,可以看到返回值:false=low value; true=high value

  • GPIOC_PINTYPE:返回GPIO的类型,具体是什么类型可以看下面的结构体定义:

enum gpio_pintype_e
{
  GPIO_INPUT_PIN = 0, /* float */
  GPIO_INPUT_PIN_PULLUP,
  GPIO_INPUT_PIN_PULLDOWN,
  GPIO_OUTPUT_PIN, /* push-pull */
  GPIO_OUTPUT_PIN_OPENDRAIN,
  GPIO_INTERRUPT_PIN,
  GPIO_INTERRUPT_HIGH_PIN,
  GPIO_INTERRUPT_LOW_PIN,
  GPIO_INTERRUPT_RISING_PIN,
  GPIO_INTERRUPT_FALLING_PIN,
  GPIO_INTERRUPT_BOTH_PIN,
  GPIO_NPINTYPES
};
  • 这个结构体是与驱动程序:/ioexpander/gpio_lower_half.c是相互对应的,所以当修改了这个结构体的时候需要修改那个文件的结构体,要相互匹配。

  • GPIOC_REGISTER:在有中断的情况下,接收GPIO的电平状态变化,说白了就是一个外部中断,检测电平变化,这个功能更多取决于平台对中断GPIO的支持。中断时要根据信号类型来做不同的反应。应该是跟信号返回值是有关的。

  • GPIOC_UNREGISTER:停止接收信号。

  • GPIOC_SETPINTYPE:可以设定GPIO的引脚类型,也就是上面那个枚举所列出来的。

接下来是中断的回调函数,学过单片机开发的应该都知道,当检测到中断信号的发生时,我们需要到回调函数中进行实现相关的操作,那么在这里,它声明了一个函数指针,所以当我们在声明中断函数的时候,需要将这个指针指向某个跟他有一样形参的函数:

struct gpio_dev_s;
typedef CODE int (*pin_interrupt_t)(FAR struct gpio_dev_s *dev, uint8_t pin);

所以在上层驱动中,我们需要传入一个gpio的结构体以及对应的引脚号进行操作。在这里补充一个点:FAR标识符,表示的是远地址,只能修饰于函数、全局变量和指针变量,对于非指针类型的局部变量,这些关键字没有实际意义。那么对于上面修饰的指针,表示的是可以寻址1MB地址空间的任意一个地方,far型指针的实质是一个32bit的整型数,高16bits为段值,低16bits为段内偏移,这个关键字受目标嵌入式设备体系结构的影响。

在这里只是声明了一个函数的框架,具体的使用可以看看这个芯片的gpio的封装,就可以一目了然了:

可以看到前面声明的那个结构体,当他传入这个结构体的指针时,它可以取到struct gpio_dev_s的首地址,所以形参就可以对上了。

在往下看,可以看到一些gpio的操作:

struct gpio_dev_s;
struct gpio_operations_s
{
  /* Interface methods */

  CODE int (*go_read)(FAR struct gpio_dev_s *dev, FAR bool *value);
  CODE int (*go_write)(FAR struct gpio_dev_s *dev, bool value);
  CODE int (*go_attach)(FAR struct gpio_dev_s *dev,
                        pin_interrupt_t callback);
  CODE int (*go_enable)(FAR struct gpio_dev_s *dev, bool enable);
  CODE int (*go_setpintype)(FAR struct gpio_dev_s *dev,
                            enum gpio_pintype_e pintype);
};
  • go_read:所有GPIO都需要的操作。

  • go_write:仅GPIO_OUTPUT_PIN引脚类型是必需的。 不用于其他引脚的类型,可能为 NULL。

  • go_attach go_enable:仅对GPIO_INTERRUPT_PIN引脚类型是必需的。 不用于其他引脚类型,可能为 NULL。

  • go_setpintype:所有GPIO都需要的操作。

0x03 Nuttx操作系统的驱动编写

驱动就是让硬件(也就是设备)正常工作,并将设备的数据接入OS中,以便于应用开发所用的程序。不同的设备有不同的驱动,原因在于设备的型号、作用、适用条件,通信接口都不尽相同,所以我们需要封装好下层驱动,上层驱动才可以统一函数来进行实现,作为一个中间件,乘上服务于应用程序,下面作用于硬件设备。

这个Nuttx的驱动分为三大类:字符驱动(PWM\CAN\TIMER等)、块设备驱动、特殊驱动(IIC\SPI\LCD\USB等)。可以在路径/nuttx/Documentation/components/drivers下看到相关的驱动文件描述,也就是那些后缀为.rst格式的文件。对于每个驱动文件,我们都得先看看他的一些描述,查看他们的index.rst文件。

(一)character-index

那么如何写驱动呢,Nuttx操作系统并没有提供像Linux系统那样复杂的驱动模型机制,比如Device、Driver、Bus、Class等。Nuttx只是简单的通过驱动注册接口,将驱动注册进文件系统中,并实现file_operation操作函数集。上层应用便能通过标准的系统调用,进而调用到低层的驱动,而底层的驱动又分为了上半部分(upper_half)和下半部分(lower_half)。

/* This structure is provided by devices when they are registered with the
 * system.  It is used to call back to perform device specific operations.
 */

struct file_operations
{
  /* The device driver open method differs from the mountpoint open method */

  int     (*open)(FAR struct file *filep);

  /* The following methods must be identical in signature and position
   * because the struct file_operations and struct mountpt_operations are
   * treated like unions.
   */

  int     (*close)(FAR struct file *filep);
  ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
  ssize_t (*write)(FAR struct file *filep, FAR const char *buffer,
                   size_t buflen);
  off_t   (*seek)(FAR struct file *filep, off_t offset, int whence);
  int     (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg);

  /* The two structures need not be common after this point */

  int     (*poll)(FAR struct file *filep, struct pollfd *fds, bool setup);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  int     (*unlink)(FAR struct inode *inode);
#endif
};

这个定义于:include/nuttx/fs/fs.h,这个文件夹中是字符驱动程序文件所需的所有结构和API。这个结构体中是使用函数集,也就是底层驱动来实现的。这个函数提供了设备的注册,并且可以通过这几个函数去操作这个设备,一切皆是文件的思想。

它的注册是使用:int register_driver(const char *path, const struct file_operations *fops, mode_t mode, void *priv);这个函数来进行注册,需要传递它的路径,这个路径是出现在ref也就是伪文件系统中,它是结构file_operations的初始化实例。

整哈书首先是根据path,比如我们的/dev/xxx来查找是否有对应的inode,如果没有的话,那就为path创建一个inode。然后将实际驱动实现的struct file_operations更新到path对应的inode中,此外还设置了权限。之后将priv数据设置仅inode中,这个一般存放驱动的私有数据。注册后,我们就可以使用标准驱动程序的用户代码访问驱动设备,比如可以使用这些函数:open(), close(), read(), write()

那么,对于其使用,比如我们要操作一个设备,第一步就是要打开相应的设备文件,即使用open()打开设备,其返回一个文件描述符fd。打开设备号之后对该设备的操作可以通过fd来完成。应用中以open()以设备的节点路径和操作权限为参数,操作进入VFS(虚拟文件系统),调用fs_open.c中的open()函数,通过设备路径找到对应的inode节点,在进程的文件描述符链表中寻找并分配空闲可用的描述符fd和文件file,最后调用设备节点inode中的文件操作符file_operation中的函数open()。应用程序调用成功时,返回本次分配的文件描述符fd,发生错误时,返回-1,错误码记录在errno中。

对于close()函数,它是用关闭设备文件,释放设备文件、文件描述符fd

对于read()函数,它是从设备读取数据,它需要三个参数,一个是文件指针,一个是数据buffer,一个是读取buffer的长度。write()函数是往设备中写数据。

对于seek()函数,它是用来查找或调整文件读写位置。

对于ioctl()函数,可以用于执行设备特定命令,如设置设备属性、配置设备寄存器等。

对于poll()函数,用于查询指定的一组文件是否可读或可写。首先初始化信号量,用于实现阻塞,直到文件可读或可写(也可以设置超时时间)。如果文件可读写,那么他会修改对应的pollfd中的返回事件标志为对应的事件;并且释放信号量。这个函数有三个参数:poll_fd数组指针,查询的文件数量,等待时间。返回正数:表示可读写的文件数量,返回0表示超时,返回-1表示错误。

这里面也有专用字符串驱动程序,有些字符驱动程序有着自己独特的字符驱动要求,这些不同于其他字符串的形式往往使用以下来进行实现:

  • 用于执行的设备特定ioctl()命令操作一些专门的设备,可以使用这个命令的,在include/nuttx下的头文件中有这个接口的详细说明:


/****************************************************************************
 * Name: ioctl
 *
 * Description:
 *   Perform device specific operations.
 *
 *   ioctl() is a non-standard UNIX-like API
 *
 * Input Parameters:
 *   fd       File/socket descriptor of device
 *   req      The ioctl command
 *   arg      The argument of the ioctl cmd, OR
 *   ...      A third argument of type unsigned long is still expected.
 *
 * Returned Value:
 *   >=0 on success (positive non-zero values are cmd-specific)
 *   -1 on failure with errno set properly:
 *
 *   EBADF
 *     'fd' is not a valid descriptor.
 *   EFAULT
 *     'arg' references an inaccessible memory area.
 *   EINVAL
 *     'cmd' or 'arg' is not valid.
 *   ENOTTY
 *     'fd' is not associated with a character special device.
 *   ENOTTY
 *      The specified request does not apply to the kind of object that the
 *      descriptor 'fd' references.
 *
 ****************************************************************************/

int ioctl(int fd, int req, ...);
  • 这个接口主要用于设备输入输出操作的系统调用,该调用传入了一个跟设备有关的请求码,系统调用的功能完全取决于请求码。

  • 专用的I/O模式。某些设备需要read()write()的操作来使一些数据传输到特定的格式,而不是普通的字节流,这个也是在include/nuttx下的头文件中有这两个接口的详细说明。

对于以上两个专用的字符驱动程序都是记录在一下的程序中:drivers/dev_null.c, drivers/fifo.c,drivers/serial.c,等等。

最后总结了这个字符驱动程序支持的设备: serial.rst

  touchscreen.rst
  analog.rst
  pwm.rst
  can.rst
  quadrature.rst
  timer.rst
  rtc.rst
  watchdog.rst
  keypad.rst
  note.rst
  foc.rst
  ws2812.rst

(二)block-index

这个块设备驱动也是有基于nclude/nuttx/fs/fs.h的一些操作,提供了一些结构体以及驱动API。具体可以看看下面的这个结构体:

struct inode;
struct block_operations
{
  int     (*open)(FAR struct inode *inode);
  int     (*close)(FAR struct inode *inode);
  ssize_t (*read)(FAR struct inode *inode, FAR unsigned char *buffer,
            blkcnt_t start_sector, unsigned int nsectors);
  ssize_t (*write)(FAR struct inode *inode, FAR const unsigned char *buffer,
            blkcnt_t start_sector, unsigned int nsectors);
  int     (*geometry)(FAR struct inode *inode, FAR struct geometry
                      *geometry);
  int     (*ioctl)(FAR struct inode *inode, int cmd, unsigned long arg);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  int     (*unlink)(FAR struct inode *inode);
#endif
};

这个结构体由块设备向系统注册时提供,文件系统使用它来执行文件系统传输每个设备节点的位置与数据,下面是它主要的函数接口:

  • int register_blockdriver(const char *path, const struct block_operations *bops, mode_t mode, void *priv);每个驱动程序都是通过调用这个函数来进行注册自己,并向其传递路径(出现在伪文件系统的路径),并且创建一个block实例。

  • 用户通常不直接访问块驱动程序,而是通过间接访问块驱动程序mount()来绑定块驱动程序,这个API具有文件系统和挂载点。然后用户可以使用块驱动程序访问文件系统上的底层媒体。可以于cmd_mount()查看实现,是在apps/nshlib/nsh_fscmds.c中实现的。

  • 也可以将字符驱动程序作为块设备访问,可以参阅drivers/loop.c

对于这个block的使用可以看下面这些文件:drivers/loop.c,drivers/mmcsd/mmcsd_spi.c, drivers/ramdisk.c

(三)special-index

对于nuttx系统的上半部分以及下半部分,使用诸如register_driver或者是register_blockdriver等调用自身注册到nuttx的上半部分,并实现相应的高级接口(read、write、driver等)。通过这些接口,可以通过上半部分调用下半部分。

(四)注册驱动函数以及解除注册驱动函数

int register_driver(FAR const char *path,
                    FAR const struct file_operations *fops,
                    mode_t mode, FAR void *priv)
{
  FAR struct inode *node;
  int ret;

  /* Insert a dummy node -- we need to hold the inode semaphore because we
   * will have a momentarily bad structure.
   */

  ret = inode_lock();
  if (ret < 0)
    {
      return ret;
    }

  ret = inode_reserve(path, mode, &node);
  if (ret >= 0)
    {
      /* We have it, now populate it with driver specific information.
       * NOTE that the initial reference count on the new inode is zero.
       */

      INODE_SET_DRIVER(node);

      node->u.i_ops   = fops;
      node->i_private = priv;
      ret             = OK;
    }

  inode_unlock();
  return ret;
}

对于参数一:设置设备路径,例如注册一个key驱动到/dev/key

对于参数二:设备的文件操作指针,指向文件操作实例。

对于参数三:预算的设备访问权限。

对于参数四:为设备驱动传递的私有参数。

返回值:0代表注册成功,返回负数,表示注册失败。

对于里面的一个函数:INODE_SET_DRIVER,底层的实现是这样的:

#define INODE_SET_TYPE(i,t) \
  do \
    { \
      (i)->i_flags = ((i)->i_flags & ~FSNODEFLAG_TYPE_MASK) | (t); \
    } \
  while (0)

#define INODE_SET_DRIVER(i)   INODE_SET_TYPE(i,FSNODEFLAG_TYPE_DRIVER)

而对于宏定义FSNODEFLAG_TYPE_DRIVER

#define   FSNODEFLAG_TYPE_DRIVER    0x00000001 /*   Character driver       */
#define   FSNODEFLAG_TYPE_BLOCK     0x00000002 /*   Block driver           */
#define   FSNODEFLAG_TYPE_MOUNTPT   0x00000003 /*   Mount point            */
#define   FSNODEFLAG_TYPE_MASK        0x0000000f /* Isolates type field      */

我猜这个是把这个node的模式配置写到某个寄存器中存储。

对于删除注册函数:

int unregister_blockdriver(FAR const char *path)
{
  int ret;

  ret = inode_lock();
  if (ret >= 0)
    {
      ret = inode_remove(path);
      inode_unlock();
    }

  return ret;
}

其实现是与Linux是相似的。最后补充两个重要的文件描述结构体以及节点的结构体:

struct inode
{
  FAR struct inode *i_parent;   /* Link to parent level inode */
  FAR struct inode *i_peer;     /* Link to same level inode */
  FAR struct inode *i_child;    /* Link to lower level inode */
  int16_t           i_crefs;    /* References to inode */
  uint16_t          i_flags;    /* Flags for inode */
  union inode_ops_u u;          /* Inode operations */
  ino_t             i_ino;      /* Inode serial number */
#ifdef CONFIG_PSEUDOFS_ATTRIBUTES
  mode_t            i_mode;     /* Access mode flags */
  uid_t             i_owner;    /* Owner */
  gid_t             i_group;    /* Group */
  struct timespec   i_atime;    /* Time of last access */
  struct timespec   i_mtime;    /* Time of last modification */
  struct timespec   i_ctime;    /* Time of last status change */
#endif
  FAR void         *i_private;  /* Per inode driver private data */
  char              i_name[1];  /* Name of inode (variable) */
};

struct file
{
  int               f_oflags;   /* Open mode flags */
  off_t             f_pos;      /* File position */
  FAR struct inode *f_inode;    /* Driver or file system interface */
  FAR void         *f_priv;     /* Per file driver private data */
};

0x04 编写LED驱动

(一)思路

在开发板上有一个RGB的灯,把他作为驱动写进/dev中,这里就先不用PWM控制了。那么从上面的分析,我们可以按照如下的流程来编写:

  • 创建字符串设备驱动主体,即文件操作file_operations,然后实现open()close()read()write()ioctl()

  • 注册该设备驱动到系统中,将会在系统的/dev目录下生成一个名为LEDBling的设备节点。

  • 通过应用程序来获取输入脚状态、控制输出脚状态。

那么首先我们先研究一下这个库的例程是怎么写的吧,并且我们也需要到底层改一下自己的引脚号,绑定自己的引脚编号,首先我们需要去board文件夹中找到自己配置时的那个型号,我的路径是这样的:


/home/zhengxiting/nuttxspace/nuttx/boards/arm/stm32/stm32f429i-disco  

那么找到这个文件:board.h,之后就可以看到很多的STM32熟悉的外设接口了。首先我们先看看例程的程序/home/zhengxiting/nuttxspace/apps/examples/gpio,首先先看懂这个程序吧:

首先包含的头文件有这么多:

#include <nuttx/config.h>

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

#include <nuttx/ioexpander/gpio.h>

这里看到它包含了上面说的gpio.h底层文件,接下来看看主程序:

首先是定义了一个指向/dev设备的指针,接下来是定义了gpio描述枚举,接下来就是各种定义标志位、还有文件描述符、检查返回值等各种变量:

  FAR char *devpath = NULL;
  enum gpio_pintype_e pintype;
  enum gpio_pintype_e newpintype;
  bool havenewtype = false;
  bool havesigno = false;
  bool invalue;
  bool outvalue = false;
  bool haveout = false;
  int signo = 0;
  int ndx;
  int ret;
  int fd;

这个程序主要实现的是通过输入参数,对GPIO进行输入输出的配置,配置的内容有如下:

static void show_usage(FAR const char *progname)
{
  fprintf(stderr, "USAGE: %s [-t <pintype>] [-w <signo>] [-o <value>] "
          "<driver-path>\n", progname);
  fprintf(stderr, "       %s -h\n", progname);
  fprintf(stderr, "Where:\n");
  fprintf(stderr, "\t<driver-path>: The full path to the GPIO pin "
          "driver.\n");
  fprintf(stderr, "\t-t <pintype>:  Change the pin to this pintype "
          "(0-10):\n");
  fprintf(stderr, "\t-w <signo>:    Wait for a signal if this is an "
          "interrupt pin.\n");
  fprintf(stderr, "\t-o <value>:    Write this value (0 or 1) if this is an "
          "output pin.\n");
  fprintf(stderr, "\t-h: Print this usage information and exit.\n");
  fprintf(stderr, "Pintypes:\n");
  fprintf(stderr, "\t 0: GPIO_INPUT_PIN\n");
  fprintf(stderr, "\t 1: GPIO_INPUT_PIN_PULLUP\n");
  fprintf(stderr, "\t 2: GPIO_INPUT_PIN_PULLDOWN\n");
  fprintf(stderr, "\t 3: GPIO_OUTPUT_PIN\n");
  fprintf(stderr, "\t 4: GPIO_OUTPUT_PIN_OPENDRAIN\n");
  fprintf(stderr, "\t 5: GPIO_INTERRUPT_PIN\n");
  fprintf(stderr, "\t 6: GPIO_INTERRUPT_HIGH_PIN\n");
  fprintf(stderr, "\t 7: GPIO_INTERRUPT_LOW_PIN\n");
  fprintf(stderr, "\t 8: GPIO_INTERRUPT_RISING_PIN\n");
  fprintf(stderr, "\t 9: GPIO_INTERRUPT_FALLING_PIN\n");
  fprintf(stderr, "\t10: GPIO_INTERRUPT_BOTH_PIN\n");
}

通过输入诸如-t\ -w \ -o等参数来配置GPIO的输入输出方式,检测输入输出的就不看了,可以看看下面的打开文件的操作:

devpath = argv[ndx];
printf("Driver: %s\n", devpath);

到了这一步他已经打开了GPIO的驱动文件。那么接下来就使用open来打开这个东西:

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

之后是输入了这个GPIO的类型:

  /* Get the pin type */

  ret = ioctl(fd, GPIOC_PINTYPE, (unsigned long)((uintptr_t)&pintype));
  if (ret < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: Failed to read pintype from %s: %d\n", devpath,
              errcode);
      close(fd);
      return EXIT_FAILURE;
    }

使用的是函数ioctl,像配置寄存器一样将我们的GPIO的类型配置进寄存器中,配置的也是刚才在执行这个应用程序时所输入的参数。

之后就是读取GPIO的状态了:

  ret = ioctl(fd, GPIOC_READ, (unsigned long)((uintptr_t)&invalue));
  if (ret < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: Failed to read value from %s: %d\n",
              devpath, errcode);
      close(fd);
      return EXIT_FAILURE;
    }

之后根据这GPIO的类型去把输入输出高低电平来写入寄存器中,从而完成GPIO的配置,这段程序也就是所有GPIO用法的例程了:

  switch (pintype)
    {
      case GPIO_INPUT_PIN:
        {
          printf("  Input pin:     Value=%u\n",
                 (unsigned int)invalue);
        }
        break;

      case GPIO_INPUT_PIN_PULLUP:
        {
          printf("  Input pin (pull-up):     Value=%u\n",
                 (unsigned int)invalue);
        }
        break;

      case GPIO_INPUT_PIN_PULLDOWN:
        {
          printf("  Input pin (pull-down):     Value=%u\n",
                 (unsigned int)invalue);
        }
        break;

      case GPIO_OUTPUT_PIN:
      case GPIO_OUTPUT_PIN_OPENDRAIN:
        {
          printf("  Output pin:    Value=%u\n", (unsigned int)invalue);

          if (haveout)
            {
              printf("  Writing:       Value=%u\n", (unsigned int)outvalue);

              /* Write the pin value */

              ret = ioctl(fd, GPIOC_WRITE, (unsigned long)outvalue);
              if (ret < 0)
               {
                 int errcode = errno;
                 fprintf(stderr,
                         "ERROR: Failed to write value %u from %s: %d\n",
                         (unsigned int)outvalue, devpath, errcode);
                 close(fd);
                 return EXIT_FAILURE;
               }

              /* Re-read the pin value */

              ret = ioctl(fd, GPIOC_READ,
                          (unsigned long)((uintptr_t)&invalue));
              if (ret < 0)
                {
                  int errcode = errno;
                  fprintf(stderr,
                          "ERROR: Failed to re-read value from %s: %d\n",
                          devpath, errcode);
                  close(fd);
                  return EXIT_FAILURE;
                }

              printf("  Verify:        Value=%u\n", (unsigned int)invalue);
            }
        }
        break;

      case GPIO_INTERRUPT_PIN:
      case GPIO_INTERRUPT_HIGH_PIN:
      case GPIO_INTERRUPT_LOW_PIN:
      case GPIO_INTERRUPT_RISING_PIN:
      case GPIO_INTERRUPT_FALLING_PIN:
      case GPIO_INTERRUPT_BOTH_PIN:
        {
          printf("  Interrupt pin: Value=%u\n", invalue);

          if (havesigno)
            {
              struct sigevent notify;
              struct timespec ts;
              sigset_t set;

              notify.sigev_notify = SIGEV_SIGNAL;
              notify.sigev_signo  = signo;

              /* Set up to receive signal */

              ret = ioctl(fd, GPIOC_REGISTER, (unsigned long)&notify);
              if (ret < 0)
                {
                  int errcode = errno;

                  fprintf(stderr,
                          "ERROR: Failed to setup for signal from %s: %d\n",
                          devpath, errcode);

                  close(fd);
                  return EXIT_FAILURE;
                }

              /* Wait up to 5 seconds for the signal */

              sigemptyset(&set);
              sigaddset(&set, signo);

              ts.tv_sec  = 5;
              ts.tv_nsec = 0;

              ret = sigtimedwait(&set, NULL, &ts);
              ioctl(fd, GPIOC_UNREGISTER, 0);

              if (ret < 0)
                {
                  int errcode = errno;
                  if (errcode == EAGAIN)
                    {
                      printf("  [Five second timeout with no signal]\n");
                      close(fd);
                      return EXIT_SUCCESS;
                    }
                  else
                    {
                      fprintf(stderr, "ERROR: Failed to wait signal %d "
                              "from %s: %d\n", signo, devpath, errcode);
                      close(fd);
                      return EXIT_FAILURE;
                    }
                }

              /* Re-read the pin value */

              ret = ioctl(fd, GPIOC_READ,
                          (unsigned long)((uintptr_t)&invalue));
              if (ret < 0)
                {
                  int errcode = errno;
                  fprintf(stderr,
                          "ERROR: Failed to re-read value from %s: %d\n",
                          devpath, errcode);
                  close(fd);
                  return EXIT_FAILURE;
                }

              printf("  Verify:        Value=%u\n", (unsigned int)invalue);
            }
        }
        break;

      default:
        fprintf(stderr, "ERROR: Unrecognized pintype: %d\n", (int)pintype);
        close(fd);
        return EXIT_FAILURE;
    }

那么看到这里,写LED的闪烁应该有头绪了,只需要配置一下GPIO,输出高低电平即可,闪烁的话搞个while来实现就行了。

(二)驱动框架搭建

首先我们nuttx/drivers的目录下新建一个我们的工程文件,里面需要包含如下文件:

  • KConfig

  • Make.defs

  • LEDBling.c

  • LEDBling.h


  

通过这两张图就可以看出这个Kconfig文件的作用了,这个是图形化的关键,所以我们要先写好这个Kconfig。

menu "LEDBling"

config MY_LED_BLING
        bool "MY_LED_BLING device support"
        default n
        ---help---
                Enable driver support for the Led Bling controler.
endmenu #LEDBling

之后就可以在make menuconfig中看到我们新建的一个驱动的功能了,还可以选择它是否开启:
  

 

需要在本文件的上一个文件的Kcongfig中加入这句:

之后就可以在菜单的驱动列表中看到我们所写的。

接下来是编写Make.defs

# LEDBling

ifeq($(CONFIG_LEDBLING) ,y)
        CSRCS += LEDBling.c
#endif

那么我们先需要把底层的驱动先写好,首先我们应该有注册函数,并且要完善好上述所说的file_operations结构体以及对这个结构体进行注册,对于led这个驱动,其实本质就是操作GPIO,我们需要得知这个GPIO的状态,让它可读可写,所以需要封装可读写可注册设备的函数结构体,所以我们的LEDBling.h需要这么编写:

#ifndef _DRIVERS_LEDBLING_H
#define _DRIVERS_LEDBLING_H

#include <nuttx/config.h>
#include <nuttx/compiler.h>
#include <nuttx/fs/ioctl.h>


#define LEDBLING_SET  0
#define LEDBLING_GET  1

struct LEDBling_status
{
    bool state;         //存储IO口状态
};

struct LEDBling_param
{
    short num;
    struct LEDBling_status on;
};

struct LEDBling_regis_s
{
    CODE int (*setio)(FAR struct file * fp ,int cmd , unsigned long arg);
    CODE int (*getio)(FAR struct file * fp ,int cmd , unsigned long arg);
    CODE int (*ioctl)(FAR struct file * fp ,int cmd , unsigned long arg);
};

int LEDBling_register(FAR const char *path , FAR void *lower);

#endif

对于C文件,其实就是对这些函数的具体实现,以及读写IO口状态的具体实现,其实都是在操作io口的底层,我们还是在上一层。

#include <nuttx/config.h>
#include <stdlib.h>
#include <fixedmath.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/sensors/LEDBling.h>

#if defined (CONFIG_LEDBLING)
static int LEDBLING_open(FAR struct file * fp);
static int LEDBLING_close(FAR struct file * fp);
static int LEDBLING_ioctl(FAR struct file * fp ,int cmd , unsigned long arg);
static int LEDBLING_read(FAR struct file * fp , FAR char * buffer ,size_t buflen);
static int LEDBLING_write(FAR struct file * fp , FAR const char* buffer , size_t buflen);

static const struct file_operations LEDBLing_op = {
    .open = LEDBLING_open;
    .close = LEDBLING_close;
    .read = LEDBLING_read;
    .write = LEDBLING_write;
    .ioctl = LEDBLING_ioctl;
}

static int LEDBLING_open(FAR struct file * fp)
{
    return OK;
}

static int LEDBLING_close(FAR struct file * fp)
{
    return OK;
}

static int LEDBLING_read(FAR struct file * fp , FAR char * buffer ,size_t buflen)
{
    return OK;
}

static int LEDBLING_write(FAR struct file * fp , FAR const char* buffer , size_t buflen)
{
    return OK;
}

static int LEDBLING_ioctl(FAR struct file * fp ,int cmd , unsigned long arg)
{
    FAR struct inode * inode  = fp -> f_inode;
    FAR struct LEDBling_regis_s *lower = inode->i_private;
    int ret = OK;

    switch(cmd)
    {
        // 这个函数是设置GPIO的状态函数
        case LEDBLING_SET:
            FAR struct LEDBling_param param = {0};
            param.num = arg & 0xFF;
            param.on.state = (arg >> 8) & 0x03;
            ret = lower -> setio(lower,param);
        break;

        case LEDBLING_GET:
            FAR struct LEDBling_status *ptr = (FAR struct LEDBling_status*)((uintptr_t)arg);
            DEBUGASSERT(ptr!=NULL);
            ret = lower-> getio(lower,ptr);
        break;

        default:
            ret  = lower -> ioctl(lower,cmd,arg);
        break;
             
    }
    return ret;
}

int LEDBling_register(FAR const char *path,FAR struct LEDBling_regis_s *dev,FAR void* lower)
{
    int ret;

    DEBUGASSERT(path != NULL);
    DEBUGASSERT(lower != NULL);

    ret = register_driver(path, &LEDBLing_op, 0666, lower);

    if (ret < 0){
        _err("ERROR: Failed to register slim led driver: %d\n", ret);
    }

    return ret;

}

写完下层驱动后,我们需要看看上层调用实现,要与具体芯片平台或者是board平台进行绑定。构建设备驱动程序要与硬件的桥梁,接下来需要去board仓库中修改如下文件:

  • Make.defs

  • config/nsh/deconfig

  • board_def.h

  • bringup.c

  • platform_data.c

  • board_LEDBling.h

  • board_LEDBling.c

那么首先我们需要在Make.defs加上下面这句:

ifeq ($(CONFIG_LEDBLING),y)
	CSRCS += board_LEDBling.c;
endif

 之后修改defconfig,路径于:/nuttxspace/nuttx/boards/arm/stm32/stm32f429i-disco/configs/nsh:

CONFIG_LEDBLING=y

更改stm32f429i-disco.h

#define BLING_LED_R       (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
						 GPIO_OUTPUT_CLEAR|GPIO_PORTH|GPIO_PIN10)
#define BLING_LED_G       (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
                         GPIO_OUTPUT_CLEAR|GPIO_PORTH|GPIO_PIN11)
#define BLING_LED_B       (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
                         GPIO_OUTPUT_CLEAR|GPIO_PORTH|GPIO_PIN12)

对应的开发板引脚号:

更改源代码的bringup.c

int stm32_bringup(void)
{
    ...
    #ifdef CONFIG_LEDBLING
    ret =  board_ledbling_initialize("/dev/ledbling",&ledbling_data,eLED_CNT);
    if(ret<0)
    {
        printf("ERROR:LedBling driver initialize failed:%d\n",ret);
    }
    #endif
    ...
}

新建board_LEDBling.h/nuttxspace/nuttx/boards/arm/stm32/stm32f429i-disco/include:

#ifndef _BOARD_LEDBLING_H
#define _BOARD_LEDBLING_H

#include <nuttx/config.h>
#include <nuttx/regulator/fixed-regulator.h>

typedef enum{
    eLED_R,
    eLED_G,
    eLED_B,
    eLED_CNT
}LEDBling_Num;

struct ledbling_platform_data{
    uint32_t pin;
    bool	 state;
};

const struct ledbling_platform_data ledbling_data[]={
    {
        .pin = BLING_LED_R,
        .state = false,
    },
    {
        .pin = BLING_LED_G,
        .state = false,
    },
    {
        .pin = BLING_LED_B,
        .state = false,
    },
} ;

extern const struct ledbling_platform_data ledbling_data[];
int board_ledbling_initialize(const char *devpath,const struct ledbling_platform_data *data,uint32_t cnt);

#endif

新建board_LEDBling.c于/nuttx/boards/arm/stm32/stm32f429i-disco/src:   
#include <nuttx/nuttx.h>
#include <nuttx/irq.h>
#include <nuttx/signal.h>
#include <debug.h>
#include <assert.h>
#include <errno.h>

#include <nuttx/err.h>
#include <nuttx/boards/arm/stm32/stm32f429i-disco/srcstm32f429i-disco.h>
#include <nuttx/boards/arm/stm32/stm32f429i-disco/include/board_LEDBling.h>
#include <nuttx/sensors/LEDBling.h>

#ifdef CONFIG_LEDBLING
static bool _get_LEDBling_state(FAR struct file * fp ,int cmd , unsigned long arg);
static void _set_LEDBling_param(FAR struct file * fp ,int cmd , unsigned long arg);

static const struct LEDBling_regis_s LEDBling_regis = {
    .getio = _get_LEDBling_state,
    .setio = _set_LEDBling_param,
    .ioctl = _set_LEDBling_param,
};

static bool _get_LEDBling_state(FAR struct file * fp ,int cmd , unsigned long arg)
{
    return LEDBLING_ioctl(fp,cmd,arg);
}

static void _set_LEDBling_param(FAR struct file * fp ,int cmd , unsigned long arg)
{
    printf("%s %d %d\r\n",__func__,param.num,param.on.state);
    if(param.num < eLED_CNT)
    {
        LEDBLING_ioctl(fp,cmd,arg);
    }
}    

int board_ledbling_initialize(const char *devpath,const struct ledbling_platform_data *data,uint32_t cnt)
{
    if(cnt>eLED_CNT){
        printf("ERROR: LED set error...\n");
        cnt = eLED_CNT;
    }
    for(uint8_t i=0;i<cnt;i++)
    {
        configgpio(pdata[i].pin);
    }
    
    return LEDBling_register(devpath,&LEDBling_regis);
}

那么到此为止,我们实现了对自己设备的驱动封装,通过函数指针去实现,完成了read\write\ioctl,完成了设备的注册,那么到此为止,我们可以生成一个/dev/ledbling的设备。那么看看应用层:

#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <fcntl.h>
#include <pthread.h>
#include <nuttx/config.h>
#include <nuttx/analog/ioctl.h>
#include <LEDBling.h>
#include <syslog.h>

typedef enum{
    eLED_R,
    eLED_G,
    eLED_B,
    eLED_CNT
}LEDBling_Pin_all;

static int led_fd = -1;

static bool _LEDBling_init(void)
{
    led_fd  = open("/dev/ledbling",O_WRONLY);
    if(led_fd<0)
    {
        printf("LEDBling open failed\n");
        return false;
    }
    return true;
}

static bool _set_ledbling_channel(LEDBling_Pin_all channel)
{
    struct ledbling_platform_data tmp[eLED_CNT] = {0};
    if(channel > eLED_CNT)
    {
        printf("param setting error...\n");
        return false;
    }
    
    if(led_fd<0)
    {
        printf("ledbling not opened yet\n");
        return _LEDBling_init();
    }
    for(uint8_t i=0;i<eLED_CNT;i++)
    {
        tmp[i] = i;
        if(i==channel)
        {
            tmp[i].on.state = true;
        }else{
            tmp[i].on.state = false;
        }
        _set_LEDBling_param(led_fd,LEDBLING_SET,tmp[i]);
    }
}

//线程操作
void *ledbling_thread(void *arg)
{
  	int ret = 0;
    int sta = 0;
    
    if(false==_LEDBling_init())	return;
    for(;;)
    {
        for(uint8_t i=0;i<eLED_CNT;i++)
        {
            _set_ledbling_channel(i);
            sleep(5);
        }
        _get_LEDBling_state(led_fd , LEDBLING_GET , &sta);
        printf("ioctl state : %d get IO state : %d",ret,sta);
    }
    close(led_fd);
    return;
}

0x05 使用已经封装好的驱动来点灯

(一)修改库文件

可以看一下目录/nuttxspace/nuttx/drivers/leds下的源文件userled_upper.c以及userled_lower.c,那么在目录下/nuttxspace/nuttx/include/nuttx/leds可以找到其userled.h,其文件中有函数:

userled_lower.c:也就是我们与嵌入式板的硬件相连接的底层文件,可以看看它的结构:

#include <nuttx/config.h>

#include <sys/types.h>
#include <assert.h>
#include <debug.h>
#include <inttypes.h>

#include <nuttx/board.h>
#include <nuttx/leds/userled.h>

包含如上头文件,那么在userled.h文件中声明的可以看看,这里封装了一些对GPIO的操作,使用宏来进行封装以及定义,接下来使用一个结构体保存led的引脚以及状态,最后声明了一个函数结构体去封装LED的操作:

struct userled_lowerhalf_s
{
  /* Return the set of LEDs supported by the board */

  CODE userled_set_t
  (*ll_supported)(FAR const struct userled_lowerhalf_s *lower);

  /* Set the current state of one LED */

  CODE void (*ll_setled)(FAR const struct userled_lowerhalf_s *lower,
                         int led, bool ledon);

  /* Set the state of all LEDs */

  CODE void (*ll_setall)(FAR const struct userled_lowerhalf_s *lower,
                         userled_set_t ledset);

#ifdef CONFIG_USERLED_LOWER_READSTATE
  /* Get the state of all LEDs */

  CODE void (*ll_getall)(FAR const struct userled_lowerhalf_s *lower,
                         userled_set_t *ledset);
#endif
};

最后也需要写一个注册函数:

int userled_register(FAR const char *devname,
                     FAR const struct userled_lowerhalf_s *lower);

那么对于userled_lower.c就是要实例化这个头文件的设置,可以看到它在这里把结构体userled_lowerhalf_s进行了实例化:

static userled_set_t
userled_supported(FAR const struct userled_lowerhalf_s *lower);
static void userled_setled(FAR const struct userled_lowerhalf_s *lower,
                           int led, bool ledon);
static void userled_setall(FAR const struct userled_lowerhalf_s *lower,
                           userled_set_t ledset);
#ifdef CONFIG_USERLED_LOWER_READSTATE
static void userled_getall(FAR const struct userled_lowerhalf_s *lower,
                           userled_set_t *ledset);
#endif

static uint32_t g_lednum;

/* This is the user LED lower half driver interface */

static const struct userled_lowerhalf_s g_userled_lower =
{
  userled_supported,    /* ll_supported */
  userled_setled,       /* ll_setled */
  userled_setall        /* ll_setall */
#ifdef CONFIG_USERLED_LOWER_READSTATE
  , userled_getall      /* ll_getall */
#endif
};

并且声明了这些函数后我们需要对其进行对gpio口操作的实现,那么这个时候就需要完成如下:

static void userled_setled(FAR const struct userled_lowerhalf_s *lower,
                           int led, bool ledon)
{
  board_userled(led, ledon);
}

static void userled_setall(FAR const struct userled_lowerhalf_s *lower,
                           userled_set_t ledset)
{
  board_userled_all(ledset);
}

static void userled_getall(FAR const struct userled_lowerhalf_s *lower,
                           userled_set_t *ledset)
{
  board_userled_getall(ledset);
}

int userled_lower_initialize(FAR const char *devname)
{
  g_lednum = board_userled_initialize();
  return userled_register(devname, &g_userled_lower);
}

有点套娃的感觉了,去看看board_userled这些函数吧,在路径/nuttxspace/nuttx/include/nuttx下可以看到这个board.h文件,这里也就只是这些函数的声明,可以在/nuttxspace/nuttx/boards/arm/stm32/stm32f429i-disco/src下找到stm32_userled.c,在其中可以看到我们的引脚的定义以及上面那些函数的实例:

 如果要使我们自己板上的LED进行闪烁变换的话,我们需要改这几个引脚的IO口就可以了,在这里我们需要去文件stm32f429i-disco.h中添加:

之后对其进行替换即可。以上就是底层的实例的文件,这个时候我们需要打开例程看看其如何操作的,具体的操作是在/nuttxspace/apps/examples/leds。打开后我们就可以看看其应用层的使用了:

对于主函数:使用task_create函数开启一个任务,并且输入我们的操作设备的函数、分配堆栈的大小:

ret = task_create("led_daemon", CONFIG_EXAMPLES_LEDS_PRIORITY,
                    CONFIG_EXAMPLES_LEDS_STACKSIZE, led_daemon,
                    NULL);

那么重点看看这个led_daemon的函数:

 

 在这里就使用了在Kconfig中提到的,由图形化界面来选择是否开启的一些宏参数:

我们现在只有一个灯,就需要吧这个EXAMPLES_LEDS_LEDSET改为0x01。之后在菜单中把这个例程开启就可以试一试了。

解决型号不匹配的问题,由于在选择型号时并没有选择自己开发板的型号,所以当烧录完系统后可能会出现晶振频率不匹配且各种频率匹配不上的问题,所以我们需要到board.h文件下修改这个晶振频率以及各种分频系数,路径为/nuttx/boards/arm/stm32/stm32f429i-disco/include,修改后的结果如下(这里使用的野火STM32F429IGT6的挑战者V1开发板):

之后修改为如下:

 

 解决打印出来会乱码的问题,首先是觉得波特率配错了,但是进入了图形化界面时,看到了波特率确实115200,并没有错,但是与minicom中的停止位不符合,于是要做出如下修改:

 

那么它就会把这个系统的打印输出到终端上。

百度一下这个错:up_assert: Assertion failed at file:armv7-m/arm_hardfault.c line: 175

那么接下来就开始编写程序吧,这里我们使用注册好的驱动/dev/userleds,首先我们需要去菜单下打开LED驱动:

 之后于Application Configuration中打开LED的例程:

 之后我们对LED的例程进行修改,在板子上有一个RGB的灯,并且我们可以得到灯的引脚号,那么我们进入stm32_userled.c中进行修改,然后在stm32f429i-disco.h中加入对应的IO口,并且更改.c文件中的各种操作指定的IO口的定义即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nuttx系统学习笔记(三)——使用Nuttx操作STM32F429外设 的相关文章

  • 普通程序员如何入门AI

    毫无疑问 xff0c 人工智能是目前整个互联网领域最火的行业 xff0c 随着AlphaGo战胜世界围棋冠军 xff0c 以及各种无人驾驶 智能家居项目的布道 xff0c 人们已经意识到了AI就是下一个风口 当然 xff0c 程序员是我见过
  • 深度学习RNN实现股票预测实战(附数据、代码)

    背景知识 最近再看一些量化交易相关的材料 xff0c 偶然在网上看到了一个关于用 RNN实现股票预测的文章 xff0c 出于好奇心把文章中介绍的代码在本地跑了一遍 xff0c 发现可以 work 于是就花了两个晚上的时间学习了下代码 xff
  • 图像拐点检测-原理以及代码实现

    今天带来的内容只用两个字形容 干货 xff01 xff01 首先我们科普下图像识别的常识 xff0c 图片在电脑看来 xff0c 其实就是一个矩阵 xff0c 每个矩阵中的一个值都对应图片的一个像素点 xff08 下图摘自 机器学习实践应用
  • c#如何实现在两个窗体(Form)间传输数据或变量

    在父窗体中显示子窗体时 xff0c 加上子窗体 Owner 61 this 在子窗体中定义一个父窗体对象 xff0c 在Load函数里面让父窗体对象 61 xff08 父窗体类型 xff09 this Owner 然后用这个父窗体对象就可以
  • docker容器webui界面之portainer

    docker通常下都是命令行管理 xff0c 不太方便 xff0c web管理能直观一点 xff0c 如果是公司有运维组 xff0c ssh账号也不用给到开发这边 单机安装命令 docker run d p 19000 9000 name
  • Windows 10安装ubuntu18.04双系统(bios和boot manager)

    1 按照网上教程制作系统盘 xff1b 2 在windows下创建空白区 xff0c 为ubuntu分配空间 xff1b 3 用做好的系统盘安装系统 由于各个厂商计算机的bios和boot manager启动键不同 xff0c 自行百度 本
  • OpenCv入门(三)——阈值化处理

    目录 0x01 OTSU 0X02 固定阈值化 0x03 自适应阈值化 0x04 双阈值化 0x05 半阈值化 在图像处理中 xff0c 处理灰度图像的计算量要小于处理彩色图像 xff0c 而二值化图像 xff08 只含灰度值0或1 xff
  • 运行gazebo时出现[Err] [REST.cc:205] Error in REST request

    出现错误 xff1a Err REST cc 205 Error in REST request libcurl 51 SSL no alternative certificate subject name matches target h
  • 安装双系统ubuntu18.04后,不能进入ubuntu界面的解决办法

    某天晚上电脑自动升级了bios xff0c 作为新手小白此时还没有意识到问题 第二天开机发现电脑不仅不能进去ubuntu xff0c 连bios都进不去了 多次开机关机重复后 xff0c 灵机一动改成重启 xff0c 终于有进入bios的界
  • Python中集合的使用

    1 set add xff09 xff1a 向集合中添加元素 xff0c 是整体添加进集合set中 xff1b set update 向集合中添加元素 xff0c update是将字符串中的拆分成字符进行追加 xff1b eg s1 61
  • Resource not found问题

    在运行ros中的xacro文件时出现的如下问题 resource not found mbot description ROS path 0 61 opt ros melodic share ros ROS path 1 61 opt ro
  • 启动Moveit Setup Assistant出错

    1 首先安装moveit配置助手 xff0c 我的ubuntu版本是18 04 xff0c 所以运行 xff1a sudo apt get install ros melodic moveit 填写个人密码安装 source opt ros
  • 形参如何改变实参

    把实参数组传递给函数 xff0c 则形参从数组那里得到了起始地址 xff0c 因此数组与实参数组共占了同一段内存单元 xff0c 在函数调用期间 xff0c 该变了形参数组的值 xff0c 也就改变了实参数组的值 例 xff1a int i
  • Linux下ARM 和单片机的串口通信设计

    摘要 xff1a 介绍Linux 环境下串口通信的设计方法和步骤 xff0c 并介绍了ARM9 微处理器s3c2440 在Linux 下和C8051Fxxx 系列单片机进行串行通信的设计方法 xff0c 给出了硬件连接和通信程序流程图 该方
  • AT+CSQ信号质量指示含义

    AT 43 CSQ 命令解释 xff1a 检查网络信号强度和SIM卡情况 命令格式 xff1a AT 43 CSQ lt CR gt 命令返回 xff1a AT 43 CSQ lt rssi gt lt ber gt 其中 lt rssi
  • MIPI接口和DVP接口的区别及优点

    DVP是并口 xff0c 需要PCLK VSYNC HSYNC D 0 xff1a 11 可以是8 10 12bit数据 xff0c 看ISP或baseband是否支持 xff1b MIPI是LVDS xff0c 低压差分串口 只需要要CL
  • 立体耳机插头和四极耳机插头三段、四段处的区别

    立体 耳机插头 和四极 耳机插头 三段 四段处的区别 在日常生活中 xff0c 通常较为细心的消费者就会发现 xff0c 适用于NOKIA手机的 耳机插头 和适用于iphone的 耳机插头 是互补兼容的 xff0c 但是iphone HTC
  • Nuttx学习笔记(一)

    最近在工作上需要用到这个nuttx实时操作系统 xff0c 并且对这个系统进行学习记录以及记录下自己所遇到过的问题 目录 一 环境配置 xff08 1 xff09 基础环境 xff08 2 xff09 下载nuttx xff08 3 xff
  • kernel command line 参数详解

    Linux内核在启动的时候 xff0c 能接收某些命令行选项或启动时参数 当内核不能识别某些硬件进而不能设置硬件参数或者为了避免内核更改某些参数的值 xff0c 可以通过这种方式手动将这些参数传递给内核 如果不使用启动管理器 xff0c 比
  • Internal error: Oops:

    01 02 00 02 24 110 SysRq Emergency Remount R O 01 02 00 02 24 221 mdss fb release all unknown process adbd pid 61 415 mf

随机推荐

  • /proc/meminfo详解

    cat proc meminfo MemTotal 2052440 kB 总内存 MemFree 50004 kB 空闲内存 Buffers 19976 kB 给文件的缓冲大小 Cached 436412 kB 高速缓冲存储器 http b
  • 浅谈Camera工作原理

    一 摄像头简介 摄像头 xff08 CAMERA xff09 又称为电脑相机 电脑眼等 xff0c 它作为一种视频输入设备 xff0c 在过去被广泛的运用于视频会议 远程医疗及实时监控等方面 近年以来 xff0c 随着互联网技术的发展 xf
  • EVT、DVT、PVT、MP等简介

    PLM xff08 Product Lifecycle Management xff09 System xff1a PLM是协助产品能够顺利完成在新产品开发 xff08 NPI xff1a New Product Introduction
  • UbuntuServer 12.04 svn服务的创建

    以下是我整理后的步骤 xff1a 1 安装必要的软件包 xff1a sudo apt get install subversion sudo apt get install libapache2 svn 2 创建一个SVN账号和SVN组 x
  • 在ESXi上把OpenWrt变成真正的路由器

    前面把openwrt装到了VMware workstation上 xff0c 本来想把openwrt直接安装到ESXi的 xff0c 但是转换镜像的时候不能生成OVF或者OVA文件 所以就先把镜像安装到了workstation xff0c
  • 电源和电池两种电源选一的芯片

  • can总线中的SOF、SRR、IDE和RTR数据位都是指什么

    帧起始 SOF xff1a 帧起始 SOF 标志着数据帧和远程帧的起始 xff0c 仅由一个 显性 位组成 仲裁域由标识符和RTR位组成 xff0c 标准帧格式与扩展帧格式的仲裁域格式不同 标准格式里 xff0c 仲裁域由1l位标识符和RT
  • linux makefile的一些变量

  • Nuttx学习笔记(二)————在STM32上部署Nuttx系统

    目录 一 平台配置 二 在ubuntu下使用串口来烧录至目标文件至STM32F07 xff08 一 xff09 ubuntu下stm32flash工具下载 xff08 二 xff09 Ubuntu20 04安装stm32开发环境 xff08
  • linux种Makefile一些自动化变量

  • arm 中的三级流水线中的PC值和当前指令的关系

  • TIM输出比较的三种模式

    TIM输出比较的三种模式 此项功能是用来控制一个输出波形 xff0c 或者指示一段给定的的时间已经到时 当计数器与捕获 比较寄存器的内容相同时 xff0c 输出比较功能做如下操作 xff1a 将输出比较模式 TIMx CCMRx寄存器中的O
  • TIM_OCMode_PWM2;TIM_OCMode_PWM1

    首先 xff0c 本人虽然初学STM32但极力反对一种误人子弟的观点 xff1a 对于STM32这样级别的MCU xff0c 有库函数就不用去看寄存器怎么操作的了 xff01 好了 xff0c 言归正传 xff0c 最近总看到很多朋友对于P
  • MOS器件的重要特性——15个为什么?

    MOS器件的重要特性 15个为什么 xff1f xff08 一 xff09 xff08 1 xff09 为什么E MOSFET的阈值电压随着半导体衬底掺杂浓度的提高而增大 xff1f 而随着温度的升高而下降 xff1f 答 E MOSFET
  • 采样频率、采样点数、频率分辨率

    1 频率分辨率的2种解释 解释一 xff1a 频率分辨率可以理解为在使用DFT时 xff0c 在频率轴上的所能得到的最小频率间隔f0 61 fs N 61 1 NTs 61 1 T 其中N为采样点数 xff0c fs为采样频率 xff0c
  • 32位单片机 一个32位地址代表一个字节而不是4个字节(32位)

    在数据手册上 xff0c BSRR的偏移地址为0X18 xff0c 然后手册讲完BSRR后直接讲LCKR了 xff0c 并且LCKR的偏移地址是 OX1C 所以根据 OX1C 0X18 61 0X04 就知道BSRR是32位寄存器了 因为一
  • μC/OS-Ⅲ系统的任务切换和任务调度

    C OS 系统的任务切换和任务调度 一 任务切换 在操作系统中当任务需要从一个任务切换到另外一个任务时 xff0c 要将当前任务的现场保存到当前任务的堆栈中 xff08 当前任务现场主要指CPU相关寄存器 xff09 xff0c 然后回复新
  • 反射系数、驻波比、S参数之间的关系

    反射系数 驻波比 S参数之间的关系 xff01 转载 回波损耗 Return Loss 入射功率 反射功率 为dB数值 反射系数 反射电压 入射电压 为标量 电压驻波比 Voltage Standing Wave Ration 波腹电压 波
  • 浅谈PWM控制电机

    先简单说说这几种模式 1 双极模式 xff0c 即电枢电压极性是正负交替的 xff0c 优点 xff1a 能正反转运行 xff0c 启动快 xff0c 调速精度高 xff0c 动态性能好 xff0c 调速静差小 xff0c 调速范围大 xf
  • Nuttx系统学习笔记(三)——使用Nuttx操作STM32F429外设

    在上一篇 xff0c 我们已经学会了如何将Nuttx进行烧录 xff0c 以及学会了如何部署这个操作系统 xff0c 接下来我们就要使用这个操作系统来实现我们对嵌入式设备的控制 xff0c 当然也是从点灯开始的 这个基于Posix架构的操作