程序小白
认证:优质创作者
所在专题目录 查看专题
单片机应用与驱动开发之路:这时的你只需要扣动扳机。
RT-Thread驱动之路:stm32设备驱动开发之uart注册①
RT-Thread驱动之路:stm32设备驱动开发之uart设备创建②
RT-Thread驱动之路:stm32设备驱动开发之uart操作方法③
RT-Thread驱动之路:stm32设备驱动开发之uart中断处理④
RT-Thread驱动之路:stm32设备驱动开发之浅析注册机制⑤
作者动态 更多
RT-Thread驱动之路: Studio初始化SPI总线③
6天前
RT-Thread驱动之路:Studio修改时钟篇②
1星期前
RT-Thread驱动之路: Studio硬件移植篇①
2星期前
RT-Thread驱动之路:stm32设备驱动开发之HWTIMER开发①
3星期前
RT-Thread驱动之路:stm32设备驱动开发之SPI总线操作方法③
3星期前

RT-Thread驱动之路:stm32设备驱动开发之uart操作方法③


      接上篇有了uart设备对象及rt_uart_ops结构体以后,我们就需要提供对应的真实的能操控硬件的函数,其实真正操作的硬件的函数并不多,创建的结构体对象如下:

static const struct rt_uart_ops stm32_uart_ops =
{
    .configure = stm32_configure,
    .control = stm32_control,
    .putc = stm32_putc,
    .getc = stm32_getc,
    .dma_transmit = stm32_dma_transmit
};

      先来看下基于hal库代码实现的stm32_configure的完整代码,当你需要更换其它单片机时,再系统没有提供对应的bsp的情况下,你可以仿照代码进行重构该函数用于适用于特殊的硬件,系统也提供了很多种不同的芯片的bsp:

static rt_err_t stm32_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    struct stm32_uart *uart;
    RT_ASSERT(serial != RT_NULL);
    RT_ASSERT(cfg != RT_NULL);

    uart = rt_container_of(serial, struct stm32_uart, serial);

    uart->handle.Instance          = uart->config->Instance;
    uart->handle.Init.BaudRate     = cfg->baud_rate;
    uart->handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    uart->handle.Init.Mode         = UART_MODE_TX_RX;
    uart->handle.Init.OverSampling = UART_OVERSAMPLING_16;
    switch (cfg->data_bits)
    {
    case DATA_BITS_8:
        uart->handle.Init.WordLength = UART_WORDLENGTH_8B;
        break;
    case DATA_BITS_9:
        uart->handle.Init.WordLength = UART_WORDLENGTH_9B;
        break;
    default:
        uart->handle.Init.WordLength = UART_WORDLENGTH_8B;
        break;
    }
    switch (cfg->stop_bits)
    {
    case STOP_BITS_1:
        uart->handle.Init.StopBits   = UART_STOPBITS_1;
        break;
    case STOP_BITS_2:
        uart->handle.Init.StopBits   = UART_STOPBITS_2;
        break;
    default:
        uart->handle.Init.StopBits   = UART_STOPBITS_1;
        break;
    }
    switch (cfg->parity)
    {
    case PARITY_NONE:
        uart->handle.Init.Parity     = UART_PARITY_NONE;
        break;
    case PARITY_ODD:
        uart->handle.Init.Parity     = UART_PARITY_ODD;
        break;
    case PARITY_EVEN:
        uart->handle.Init.Parity     = UART_PARITY_EVEN;
        break;
    default:
        uart->handle.Init.Parity     = UART_PARITY_NONE;
        break;
    }

#ifdef RT_SERIAL_USING_DMA
    uart->dma_rx.last_index = 0;
#endif

    if (HAL_UART_Init(&uart->handle) != HAL_OK)
    {
        return -RT_ERROR;
    }

    return RT_EOK;
}

      代码很长,但是功能很简单就是对串口进行初始化,设置串口波特率,硬件流控,串口模式、串口采样、数据位、停止位、DMA相关参数、最后调用HAL_UART_Init()完成串口的初始化功能。


      接下来看下stm32_control函数的代码,代码虽长单功能并不复杂:

static rt_err_t stm32_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct stm32_uart *uart;
#ifdef RT_SERIAL_USING_DMA
    rt_ubase_t ctrl_arg = (rt_ubase_t)arg;
#endif

    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct stm32_uart, serial);

    switch (cmd)
    {
    /* disable interrupt */
    case RT_DEVICE_CTRL_CLR_INT:
        /* disable rx irq */
        NVIC_DisableIRQ(uart->config->irq_type);
        /* disable interrupt */
        __HAL_UART_DISABLE_IT(&(uart->handle), UART_IT_RXNE);

#ifdef RT_SERIAL_USING_DMA
        /* disable DMA */
        if (ctrl_arg == RT_DEVICE_FLAG_DMA_RX)
        {
            HAL_NVIC_DisableIRQ(uart->config->dma_rx->dma_irq);
            if (HAL_DMA_Abort(&(uart->dma_rx.handle)) != HAL_OK)
            {
                RT_ASSERT(0);
            }

            if (HAL_DMA_DeInit(&(uart->dma_rx.handle)) != HAL_OK)
            {
                RT_ASSERT(0);
            }
        }
        else if(ctrl_arg == RT_DEVICE_FLAG_DMA_TX)
        {
            HAL_NVIC_DisableIRQ(uart->config->dma_tx->dma_irq);
            if (HAL_DMA_DeInit(&(uart->dma_tx.handle)) != HAL_OK)
            {
                RT_ASSERT(0);
            }
        }
#endif
        break;
    /* enable interrupt */
    case RT_DEVICE_CTRL_SET_INT:
        /* enable rx irq */
        HAL_NVIC_SetPriority(uart->config->irq_type, 1, 0);
        HAL_NVIC_EnableIRQ(uart->config->irq_type);
        /* enable interrupt */
        __HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_RXNE);
        break;

#ifdef RT_SERIAL_USING_DMA
    case RT_DEVICE_CTRL_CONFIG:
        stm32_dma_config(serial, ctrl_arg);
        break;
#endif

    case RT_DEVICE_CTRL_CLOSE:
        if (HAL_UART_DeInit(&(uart->handle)) != HAL_OK )
        {
            RT_ASSERT(0)
        }
        break;

    }
    return RT_EOK;
}

      主要看一下几个控制参数:RT_DEVICE_CTRL_CLR_INT关闭中断,如果有使用dma则关闭DMA。RT_DEVICE_CTRL_SET_INT:开启终端,如果有使用dma则采用dma_config功能打开dma。RT_DEVICE_CTRL_CLOSE:关闭串口,就这几个功能。


      发送函数功能最简单,轻松TC中断标记,将数据放入DR数据寄存器中,while循环等待TC中断触发(DR数据寄存器为空):

static int stm32_putc(struct rt_serial_device *serial, char c)
{
    struct stm32_uart *uart;
    RT_ASSERT(serial != RT_NULL);

    uart = rt_container_of(serial, struct stm32_uart, serial);
    UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \
    || defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) \
    || defined(SOC_SERIES_STM32G4)
    uart->handle.Instance->TDR = c;
#else
    uart->handle.Instance->DR = c;
#endif
    while (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);
    return 1;
}

      接下来是读取数据,这里有个有意思的现象,看过源码你会发现读取数据和写入数据访问的实际的硬件寄存器都是一样的,DR or RDR,那么你会不会有个疑问,那么某一刻读取的数据会不会是刚写入的数据,代码之后揭晓:

static int stm32_getc(struct rt_serial_device *serial)
{
    int ch;
    struct stm32_uart *uart;
    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct stm32_uart, serial);

    ch = -1;
    if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET)
    {
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \
    || defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) \
    || defined(SOC_SERIES_STM32G4)
        ch = uart->handle.Instance->RDR & 0xff;
#else
        ch = uart->handle.Instance->DR & 0xff;
#endif
    }
    return ch;
}

      揭晓答案啦:这里以DR为例,其实它是一对神奇的双胞胎,一个名字对应着两个真实的寄存器,当你写入数据时实际写入的是发送DR,当你读取数据时实际访问的是接收DR,是通过你对硬件的读or写操作,硬件自行对应的,当年在这个问题上耗死了好些的脑细胞,也不知道是哪个天才设计的机制。


      最后到了还有连续传送字节(发送)的实现,借助的硬件的DMA技术实现的:

static rt_size_t stm32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction)
{
    struct stm32_uart *uart;
    RT_ASSERT(serial != RT_NULL);
    RT_ASSERT(buf != RT_NULL);
    uart = rt_container_of(serial, struct stm32_uart, serial);

    if (size == 0)
    {
        return 0;
    }

    if (RT_SERIAL_DMA_TX == direction)
    {
        if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK)
        {
            return size;
        }
        else
        {
            return 0;
        }
    }
    return 0;
}

      带着问题结束本篇吧,发送很简单 一个接一个的发送出去,那么接收呢,DR中有没有数据,是不是需要频繁的触发读取函数,或者读不及时会不会被下一个来的数据将DR中的数据覆盖,或者数据直接被丢掉呢?

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 2
收藏 3
关注 140
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧