RT-Thread 实时操作系统学习笔记-USART串口

I/O 设备管理函数: usart.c

1. 概述

usart是嵌入式设备最为常用的功能,本文以usart为例来解读一下RT-Thread操作系统和我们平时直接操作库函数甚至寄存器的使用方法有什么不同。
RT-Thread关于usart的文件主要两处,Drivers里面的usart.c和usart.h,DeviceDrive下的serial.c和serial.h。前者较后者在RT-Thread中的更加偏底层一些,我们熟悉的操作主要在usart.c和usart.h中。
usart.c中的函数分为两种

  • 和 stm32底层硬件较为相关的函数
  • 和RT-Thread上层I/O设备接口较为相关的函数

前者和网上对stm32进行硬件操作的教程较为类似,主要函数如下:

static void RCC_Configuration(void)
static void GPIO_Configuration(void)
static void NVIC_Configuration(struct stm32_uart *uart)
void USARTx_IRQHandler(void)          x = 1,2,3...

后者和RT-Thread紧密相关,主要为定义在device.c中的设备抽象层服务,主要有:

static rt_err_t stm32_configure(struct rt_serial_device *serial, struct serial_configure *cfg) 
static rt_err_t stm32_control(struct rt_serial_device *serial, int cmd, void *arg) 
static int stm32_putc(struct rt_serial_device *serial, char c)
static int stm32_getc(struct rt_serial_device *serial)

在定义完上述四个配置函数之后,紧接着定义串口抽象函数的服务结构体:

static const struct rt_uart_ops stm32_uart_ops =
{
    stm32_configure,
    stm32_control,
    stm32_putc,
    stm32_getc,
};

RT-Thread是纯C语言编写的嵌入式操作系统,必然需要涉及面向对象的操作,但是C++的某些特性的实现又是不可控的,为避免隐患,需要用C实现面向对象的操作。
在实现的对某些对象(如串口)的方法时,采用关键字static可以把函数的作用范围局限在一个文件内部,起到像类一样封装和隐藏内部实现的作用。
这些抽象层的函数是对所有device有效的,由基类的rt_device派生出来。之所以为抽象,就是不用考虑底层的具体硬件实现,只需要保证所谓的这个设备能够configure、control、putc(发送数据)、getc(接受数据)等等即可。
比如可以用stm32_putc定义USART的发送数据的方法,也可以用stm32_putc定义SPI发送数据的方法。但是有个疑问,用同一个函数去定义,这些方法难道不会冲突吗?这就考虑到多态性的问题了。

对象根据所接收的消息而做出动作。同一消息为不同的对象接受时可产生完全不同的行动,这种现象称为多态性。... .... RT-Thread系统中的设备:抽象设备具备接口统一的读写接口。串口是设备的一种,也应支持设备的读写。但串口的读写操作是串口所特有的,不应和其他设备操作完全相同,例如操作串口的操作不应应用于SD卡设备中。多态性的实现受到继承性的支持,利用类继承的层次关系,把具有通用功能的协议存放在类层次中尽可能高的地方,而将实现这一功能的不同方法置于较低层次,这样,在这些低层次上生成的对象就能给通用消息以不同的响应。

stm32_putc 为例struct rt_serial_device *serial 是其传递进去的函数,struct rt_serial_device在serial.h中定义,serial.h和serial.c是串口服务函数的抽象层,不仅适用于stm32的usart,也适用其他单片机的usart,这样可以较为方便兼容各种类型的单片机,具体定义如下:

struct rt_serial_device
{
    struct rt_device          parent;

    const struct rt_uart_ops *ops;
    struct serial_configure   config;

    void *serial_rx;
    void *serial_tx;
};
typedef struct rt_serial_device rt_serial_t;

可以看到,struct rt_serial_devicestruct rt_device派生出来,struct rt_device在rtdef.h中定义:

/**
 * Device structure
 */
struct rt_device
{
    struct rt_object          parent;                   /**< inherit from rt_object */

    /* 设备类型 */
    enum rt_device_class_type type;                     /**< device type */
    /* 设备参数及打开参数 */
    rt_uint16_t               flag;                     /**< device flag */
    rt_uint16_t               open_flag;                /**< device open flag */

    rt_uint8_t                ref_count;                /**< reference count */
    rt_uint8_t                device_id;                /**< 0 - 255 */

    /* 提供给上层应用的回调函数 */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

    /* 公共的设备接口(由驱动程序提供) */
    rt_err_t  (*init)   (rt_device_t dev);
    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);
    rt_err_t  (*close)  (rt_device_t dev);
    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
    rt_err_t  (*control)(rt_device_t dev, rt_uint8_t cmd, void *args);

    void                     *user_data;                /**< device private data */
};

** struct rt_device是RT-Thread的基本的I/O 设备控制块,里面规定了一个device几乎所有可能的操作及状态**。注意由驱动程序提供的公共的设备接口,包括了初始化、打开、关闭、读、写、控制,这6种操作是RT-Thread认为一个设备足够用的实现方法,我们也可以自行修改。
stm32的usart的操作跟这些函数对应起来,就将RT-Thread的上层操作和底层的硬件操作联系起来。以后使用其他芯片的usart,只要在相应的硬件函数实现的地方修改一下即可,不用改RT-Thread上层的代码。小到嵌入式,大到Linux这样的操作系统,都是这样的逻辑。

RT-Thread 实时操作系统学习笔记-USART串口_第1张图片
Paste_Image.png

rt_device 继承自struct rt_objectrt_object 较为简单:

/**
 * Base structure of Kernel object
 */
struct rt_object
{
    char       name[RT_NAME_MAX];                       /**< name of kernel object */
    rt_uint8_t type;                                    /**< type of kernel object */
    rt_uint8_t flag;                                    /**< flag of kernel object */

#ifdef RT_USING_MODULE
    void      *module_id;                               /**< id of application module */
#endif
    rt_list_t  list;                                    /**< list node of kernel object */
};
RT-Thread 实时操作系统学习笔记-USART串口_第2张图片
Paste_Image.png

2. 串口设备硬件初始化

stm32_hw_usart_init 函数是整个usart.c的入口函数,是系统初始化的一步,其执行的顺序相对要靠前很多。
系统上电之后,首先执行startup.c下的main函数

int main(void)
{
    /* disable interrupt first */ // 关中断
    rt_hw_interrupt_disable();

    /* startup RT-Thread RTOS */  //运行RTOS
    rtthread_startup();

    return 0;
}

第一步关中断,避免初始化过程被打断。rtthread_startup 是RT-Thread启动的第一步,首先进行的就是硬件初始化

void rtthread_startup(void)
{
    /* init board */
    rt_hw_board_init();
   ...其余略 ...
}

rt_hw_board_init 定义在board.c中,我们可以根据自己需要自行更改,比如在RT-thread 2.0.0正式版中引入了pin设备作为杂类设备,其设备驱动文件pin.c在rt-thread-2.0.1\components\drivers\misc中,主要用于操作芯片GPIO, 如点亮led,按键等。我们可以根据需要在rtconfig.h中选择是否使用pin设备。

/* Using GPIO pin framework */
#define RT_USING_PIN

如果用RT-Thread官方推荐的建构工具SCons自动组织工程的话,这句话的作用其实就是将gpio.c和gpio.h两个文件加入到当前工程Drivers文件夹内,并在DeviceDrivers文件夹内添加pin.c和pin.h。添加一个外设要添加相应的底层驱动和设备驱动。同时在硬件初始化时在rt_hw_board_init内添加pin设备硬件初始化的函数stm32_hw_pin_init

void rt_hw_board_init()
{
    /* NVIC Configuration */
    NVIC_Configuration();

    /* Configure the SysTick */
    SysTick_Configuration();

    stm32_hw_usart_init();
    stm32_hw_pin_init();
    
#ifdef RT_USING_CONSOLE
    rt_console_set_device(CONSOLE_DEVICE);
#endif
}

接着usart的入口函数stm32_hw_usart_init讲起。

int stm32_hw_usart_init(void)
{
    struct stm32_uart *uart;
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;

    RCC_Configuration();
    GPIO_Configuration();

#ifdef RT_USING_UART1
    uart = &uart1;

    serial1.ops    = &stm32_uart_ops;
    serial1.config = config;

    NVIC_Configuration(&uart1);

    /* register UART1 device */
    rt_hw_serial_register(&serial1,
                          "uart1",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                          uart);
#endif /* RT_USING_UART1 */
}

RCC_Configuration()GPIO_Configuration()NVIC_Configuration(&uart1) 分别用来打开外设时钟、配置I/O 口和设置串口中断。
struct stm32_uart *uart 前面带有stm32 说明调用到的是stm32的官方库函数,或者是跟硬件直接相关的量。事实上uart1是个依托库函数自定义的结构体

/* STM32 uart driver */
struct stm32_uart
{
    USART_TypeDef *uart_device;
    IRQn_Type irq;
};

接着接着初始化设备类对象serial1的ops和config两个参数,ops的四个实现 stm32_configure、stm32_control、stm32_putc、stm32_getc都在usart.c中实现。

    serial1.ops    = &stm32_uart_ops;
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    serial1.config = config;

名字前面有serial,说明是跟RT-Thread上层有直接关系的结构体。看名字能猜到是与usart配置相关的定义。其中RT_SERIAL_CONFIG_DEFAULT 定义在serial.h,定义了默认的波特率等数据。

/* Default config for serial_configure structure */
#define RT_SERIAL_CONFIG_DEFAULT           \
{                                          \
    BAUD_RATE_115200, /* 115200 bits/s */  \
    DATA_BITS_8,      /* 8 databits */     \
    STOP_BITS_1,      /* 1 stopbit */      \
    PARITY_NONE,      /* No parity  */     \
    BIT_ORDER_LSB,    /* LSB first sent */ \
    NRZ_NORMAL,       /* Normal mode */    \
    RT_SERIAL_RB_BUFSZ, /* Buffer size */  \
    0                                      \
}

最后注册串口设备serial1:

    /* register UART1 device */
    rt_hw_serial_register(&serial2,
                          "uart2",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                          uart);

rt_hw_serial_register 函数位于serial.c中。

3. 将usart纳入RT-Thread的I/O 设备层

相对于stm32的内核来说,USART是一种低速的串行设备,并且为了最大的发挥的MCU的性能,因此使用查询方式发送、中断方式接收(发送也可以使用DMA方式)。这些已经在usart.c中使能了。

(未完待续……)

你可能感兴趣的:(RT-Thread 实时操作系统学习笔记-USART串口)