爱新觉罗是什么意思| 眉毛里有痣代表什么| 凉拖鞋什么材质的好| 这是什么鱼| 3岁小孩不会说话是什么原因| 声优是什么意思| 达摩是什么意思| 咽炎吃什么药效果好| 梦见兔子是什么预兆| 堤防是什么意思| 几天不大便是什么原因| 二尖瓣微量反流什么意思| 男人左手断掌是什么命| 夏天哈尔滨有什么好玩的地方| 胡萝卜什么时间种| 淋巴结是什么东西| 流汗有什么好处| 恶露是什么样子的图片| 农历六月十四是什么星座| 胃反酸吃什么药| rbc红细胞偏高是什么意思| 卵巢囊肿是什么| 面基是什么意思| 标本是什么意思| 囊肿与肿瘤有什么区别| 生蚝吃了有什么好处| 五灵脂是什么东西| ader是什么牌子| 黄连是什么| 地藏菩萨的坐骑是什么| 舟可是什么字| 铁皮石斛可以治什么病| 晨尿泡沫多是什么原因| 没什么大不了的| 四方八面是什么生肖| 太原为什么叫龙城| 风肖是什么生肖| 襁褓是什么意思| 妈祖属什么生肖| 什么是历史虚无主义| 什么叫酮症酸中毒| 世界上最深的湖是什么| 处级是什么级别| 吃什么滋阴效果最好| 什么可以| 什么是冰种翡翠| 麻醉科属于什么科室| 长白头发缺什么维生素| 10万个为什么| 吃南瓜子有什么好处| 李宇春父亲是干什么的| 黑裤子配什么颜色的鞋| 脚后跟痛是什么原因| 成吉思汗姓什么| 5月11号是什么星座| 白天不咳嗽晚上咳嗽吃什么药| 建议是什么意思| gg是什么品牌| 血糖高注意什么| 经常眩晕是什么原因引起的| 嘱托是什么意思| 紫癜吃什么好得快| 临床医生是什么意思| 男的纹般若有什么寓意| 疱疹用什么药可以根治| 小说be是什么意思| 尿电导率低是什么意思| 钢铁侠叫什么名字| 做俯卧撑有什么好处| 梦见家里着火了是什么征兆| 电解液是什么| 属牛的和什么属相最配| 乙肝25阳性什么意思| 圣女果是什么水果| 数字9像什么| 高干文是什么意思| 冰粉是什么做的| 肾水不足是什么意思| 欢五行属什么| 嘴唇有黑斑是什么原因| 肤专家软膏主要治什么| 骨盆倾斜有什么症状| 1927年中国发生了什么| 减脂早餐吃什么| 广西产什么水果| 为什么会一直咳嗽| 晚上睡觉口干是什么原因| 夏至喝什么汤| 硬化萎缩性苔藓是什么病| 尿潜血1十是什么原因| 什么是消融手术| 贪污是什么意思| 脾虚湿蕴证是什么意思| 家里为什么会有蟑螂| 什么食物含叶酸多| 喝黑咖啡有什么好处| 月经时间过长是什么原因引起的| 高密度脂蛋白胆固醇偏低什么意思| 宽宽的什么填空| 肛瘘挂什么科| 早泄吃什么中药| 火乐读什么| 腿脚肿胀是什么原因引起的| 抠是什么意思| 耳鸣是什么原因| 闺房是什么意思| 狗肉炖什么好吃| 紫水晶属于五行属什么| 角膜炎吃什么药| 杨桃是什么季节的水果| 暗物质和暗能量是什么| 通五行属什么| 24节气是什么| 为什么会中暑| 黄柏胶囊主要治什么病| 3楼五行属什么| Years什么意思| 甲状腺疾病有什么症状| 花什么叶什么| 桜什么意思| 梦见奶奶死了是什么意思| 怀孕前三个月需要注意什么| 君主是什么意思| 老鼠的尾巴有什么作用| ab型血可以接受什么血型| 人总放屁是什么原因| 红斑狼疮是什么原因引起的| 头经常晕是什么原因| 病毒感染会有什么症状| 经期不能吃什么| 冻顶乌龙茶属于什么茶| 白羊属于什么象星座| 息肉病变什么意思| 黄油可以做什么美食| 吃什么盐比较好有利于健康| 夏季喝什么茶好| 梦见自己生了个女孩是什么意思| 什么的爱心| exo什么时候出道的| 拆线去医院挂什么科| 手臂上长痣代表什么| 尿液里白细胞高是什么原因| 七十岁老人装什么牙合适| 肺炎吃什么药效果好| 十二指肠球炎是什么意思| 外感风寒是什么意思| 胃溃疡吃什么好| kobe是什么意思| 抬头纹开了是什么意思| 牛磺酸有什么作用| icp是什么| 皮肤黑适合什么颜色的衣服| 眼睛有点黄是什么原因| 提心吊胆是什么生肖| 河粉为什么叫河粉| 降钙素原检测是查什么的| 人体缺钠会出现什么症状| 小白龙叫什么| 下嘴唇发麻什么病兆| 肝火旺吃什么药好| 喝什么排湿气| 大象是什么意思| tpa是什么意思| 囊肿是什么| 今年是农历的什么年| 凌晨两点是什么时辰| 小肚子胀气是什么原因| 左附件囊肿注意什么| edifier是什么牌子| 红线是什么意思| 肝脏低密度灶是什么意思| 砧板是什么工作| 人为什么会打哈欠| 黄忠字什么| 绿心黑豆有什么功效| 夏天脚底出汗是什么原因| 腿硬邦邦的是什么原因| 地主之谊是什么意思| gg是什么品牌| 椰子不能和什么一起吃| 吃什么能提高血压| 6月20日是什么星座| 说话不清楚去医院挂什么科| 吃红糖有什么好处和坏处| 退役和退伍有什么区别| 男人皮肤黑穿什么颜色的衣服好看| 闭关什么意思| 高压氧舱治疗什么效果| 黄皮什么时候上市| 小狗什么时候换牙| jeep是什么牌子| 怀孕什么不能吃| 拉磨是什么意思| 口腔医学学什么| 什么是幽门螺杆菌感染| 踏青是什么意思| 夏天吃什么水果好| 中耳炎挂什么科| 胎心停了是什么原因引起的| 乌龟和鳖有什么区别| RHD血型阳性什么意思| 智能手环什么品牌好| 霰粒肿用什么药| 两点一线是什么意思| ost是什么意思| 什么叫人均可支配收入| 富甲一方什么意思| 绿是什么| 冬至说什么祝福语| 做蛋糕用什么面粉| 铁蛋白低吃什么可以补| 感冒适合吃什么水果| 暗财是什么意思| 梵克雅宝为什么那么贵| 测怀孕什么时候最准| 原发性高血压什么意思| 面瘫挂什么科室| 脚癣是什么原因引起的| 跟着好人学好人下句是什么| 冷面是用什么面做的| 零和游戏是什么意思| 潘粤明老婆现任叫什么| 咳血是什么原因| 柏油是什么| 王维被称为什么| 维生素b12有什么用| 魔改是什么意思| 炸酥肉用什么粉| 敢是什么意思| 39岁属什么| 3.2号是什么星座| 1993年什么命| 猩红热是什么| 为什么会有月经| 6月12日什么星座| 晚上睡觉咳嗽是什么原因| 四大天王叫什么名字| 乌龟为什么会叫| 青海是什么省| 饭后胃胀是什么原因导致的| 春饼卷什么菜好吃| 眉下有痣代表什么| 什么叫荨麻疹| 什么是口播| 顺字五行属什么| 指甲有凹陷是什么原因| 口蘑是什么| 全身无力吃什么药| 龙是什么意思| 74年属什么| 湿疹挂什么科| 低血糖有什么症状表现| 腋下有异味是什么原因导致的| 做梦梦见马是什么意思| 厚黑学讲的是什么| strange是什么意思| 为什么男生喜欢女生叫爸爸| 尿道炎看什么科室好| 身上冷是什么原因| 双肾尿盐结晶是什么意思| 女生喜欢什么礼物| 羊与什么生肖相合| 指甲上白色月牙代表什么| 龙日冲狗要忌讳什么| 百度
这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 ? 论坛首页 ? 嵌入式开发 ? 软件与操作系统 ? rtthread串口框架V1

共3条 1/1 1 跳转至

rtthread串口框架V1

高工
2025-08-04 20:06:46     打赏
百度 因为成立退役军人事务部以后,将解除现役军人的后顾之忧,专心致志地练兵备战,心无旁骛地谋打赢。

前言

   RTT存在两版串口框架,而从bsp目录的适配情况看,两版串口框架都有新bsp适配使用,因此两套框架都需要学习。

       由于V2相比较于V1,改动还挺多,因此学习时,将两套框架分开学习。

代码解析

   V1框架的代码主要放置于/components/drivers/serial/serial.c中,因此学习也是围绕该文件而展开的。

串口注册入口

     串口注册入口其实挺好找,在RTT设备框架里,所有设备都是基于device框架实现的,因此只需要在框架文件中搜索 struct rt_device_ops ,再反查便可定位到注册入口,具体注册入口如下:

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops serial_ops =
{
    rt_serial_init,
    rt_serial_open,
    rt_serial_close,
    rt_serial_read,
    rt_serial_write,
    rt_serial_control
};
#endif

/*
 * serial register
 */
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
                               const char              *name,
                               rt_uint32_t              flag,
                               void                    *data)
{
    rt_err_t ret;
    struct rt_device *device;
    RT_ASSERT(serial != RT_NULL);

    rt_spin_lock_init(&(serial->spinlock));

    device = &(serial->parent);

    device->type        = RT_Device_Class_Char;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
    device->ops         = &serial_ops;
#else
    device->init        = rt_serial_init;
    device->open        = rt_serial_open;
    device->close       = rt_serial_close;
    device->read        = rt_serial_read;
    device->write       = rt_serial_write;
    device->control     = rt_serial_control;
#endif
    device->user_data   = data;

    /* register a character device */
    ret = rt_device_register(device, name, flag);

#ifdef RT_USING_POSIX_STDIO
    /* set fops */
    device->fops        = &_serial_fops;
#endif

#if defined(RT_USING_SMART)
    rt_hw_serial_register_tty(serial);
#endif

    return ret;
}

       观察注册入口,其实就可以发现,里面主要干了这么几件事,初始化自旋锁(作用得后面分析才清楚),标准的设备注册操作,最后是特定功能的特定入口注册,由于现在所使用的平台是非RTSmart的平台,因此关于RT_USING_SMART也就不去分析了。

串口初始化入口

static rt_err_t rt_serial_init(struct rt_device *dev)
{
    rt_err_t result = RT_EOK;
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    serial = (struct rt_serial_device *)dev;

    /* initialize rx/tx */
    serial->serial_rx = RT_NULL;
    serial->serial_tx = RT_NULL;

    rt_memset(&serial->rx_notify, 0, sizeof(struct rt_device_notify));

    /* apply configuration */
    if (serial->ops->configure)
        result = serial->ops->configure(serial, &serial->config);

    return result;
}

    从初始化入口上看,实际上就是提前初始化串口所需要的变量,以防后面逻辑跑乱。另外,调用了驱动层的configure入口,将当前串口的配置信息传递至驱动层实现。

串口打开入口

static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag)
{
    rt_uint16_t stream_flag = 0;
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    serial = (struct rt_serial_device *)dev;

    LOG_D("open serial device: 0x%08x with open flag: 0x%04x",
        dev, oflag);
    /* check device flag with the open flag */
    if ((oflag & RT_DEVICE_FLAG_DMA_RX) && !(dev->flag & RT_DEVICE_FLAG_DMA_RX))
        return -RT_EIO;
    if ((oflag & RT_DEVICE_FLAG_DMA_TX) && !(dev->flag & RT_DEVICE_FLAG_DMA_TX))
        return -RT_EIO;
    if ((oflag & RT_DEVICE_FLAG_INT_RX) && !(dev->flag & RT_DEVICE_FLAG_INT_RX))
        return -RT_EIO;
    if ((oflag & RT_DEVICE_FLAG_INT_TX) && !(dev->flag & RT_DEVICE_FLAG_INT_TX))
        return -RT_EIO;

    /* keep steam flag */
    if ((oflag & RT_DEVICE_FLAG_STREAM) || (dev->open_flag & RT_DEVICE_FLAG_STREAM))
        stream_flag = RT_DEVICE_FLAG_STREAM;

    /* get open flags */
    dev->open_flag = oflag & 0xff;

#ifdef RT_USING_PINCTRL
    /* initialize iomux in DM */
    rt_pin_ctrl_confs_apply_by_name(dev, RT_NULL);
#endif

    /* initialize the Rx/Tx structure according to open flag */
    if (serial->serial_rx == RT_NULL)
    {
        if (oflag & RT_DEVICE_FLAG_INT_RX)
        {
            struct rt_serial_rx_fifo* rx_fifo;

            rx_fifo = (struct rt_serial_rx_fifo*) rt_malloc (sizeof(struct rt_serial_rx_fifo) +
                serial->config.bufsz);
            RT_ASSERT(rx_fifo != RT_NULL);
            rx_fifo->buffer = (rt_uint8_t*) (rx_fifo + 1);
            rt_memset(rx_fifo->buffer, 0, serial->config.bufsz);
            rx_fifo->put_index = 0;
            rx_fifo->get_index = 0;
            rx_fifo->is_full = RT_FALSE;

            serial->serial_rx = rx_fifo;
            dev->open_flag |= RT_DEVICE_FLAG_INT_RX;
            /* configure low level device */
            serial->ops->control(serial, RT_DEVICE_CTRL_SET_INT, (void *)RT_DEVICE_FLAG_INT_RX);
        }
#ifdef RT_SERIAL_USING_DMA
        else if (oflag & RT_DEVICE_FLAG_DMA_RX)
        {
            if (serial->config.bufsz == 0) {
                struct rt_serial_rx_dma* rx_dma;

                rx_dma = (struct rt_serial_rx_dma*) rt_malloc (sizeof(struct rt_serial_rx_dma));
                RT_ASSERT(rx_dma != RT_NULL);
                rx_dma->activated = RT_FALSE;

                serial->serial_rx = rx_dma;
            } else {
                struct rt_serial_rx_fifo* rx_fifo;

                rx_fifo = (struct rt_serial_rx_fifo*) rt_malloc (sizeof(struct rt_serial_rx_fifo) +
                    serial->config.bufsz);
                RT_ASSERT(rx_fifo != RT_NULL);
                rx_fifo->buffer = (rt_uint8_t*) (rx_fifo + 1);
                rt_memset(rx_fifo->buffer, 0, serial->config.bufsz);
                rx_fifo->put_index = 0;
                rx_fifo->get_index = 0;
                rx_fifo->is_full = RT_FALSE;
                serial->serial_rx = rx_fifo;
                /* configure fifo address and length to low level device */
                serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *) RT_DEVICE_FLAG_DMA_RX);
            }
            dev->open_flag |= RT_DEVICE_FLAG_DMA_RX;
        }
#endif /* RT_SERIAL_USING_DMA */
        else
        {
            serial->serial_rx = RT_NULL;
        }
    }
    else
    {
        if (oflag & RT_DEVICE_FLAG_INT_RX)
            dev->open_flag |= RT_DEVICE_FLAG_INT_RX;
#ifdef RT_SERIAL_USING_DMA
        else if (oflag & RT_DEVICE_FLAG_DMA_RX)
            dev->open_flag |= RT_DEVICE_FLAG_DMA_RX;
#endif /* RT_SERIAL_USING_DMA */
    }

    if (serial->serial_tx == RT_NULL)
    {
        if (oflag & RT_DEVICE_FLAG_INT_TX)
        {
            struct rt_serial_tx_fifo *tx_fifo;

            tx_fifo = (struct rt_serial_tx_fifo*) rt_malloc(sizeof(struct rt_serial_tx_fifo));
            RT_ASSERT(tx_fifo != RT_NULL);

            rt_completion_init(&(tx_fifo->completion));
            serial->serial_tx = tx_fifo;

            dev->open_flag |= RT_DEVICE_FLAG_INT_TX;
            /* configure low level device */
            serial->ops->control(serial, RT_DEVICE_CTRL_SET_INT, (void *)RT_DEVICE_FLAG_INT_TX);
        }
#ifdef RT_SERIAL_USING_DMA
        else if (oflag & RT_DEVICE_FLAG_DMA_TX)
        {
            struct rt_serial_tx_dma* tx_dma;

            tx_dma = (struct rt_serial_tx_dma*) rt_malloc (sizeof(struct rt_serial_tx_dma));
            RT_ASSERT(tx_dma != RT_NULL);
            tx_dma->activated = RT_FALSE;

            rt_data_queue_init(&(tx_dma->data_queue), 8, 4, RT_NULL);
            serial->serial_tx = tx_dma;

            dev->open_flag |= RT_DEVICE_FLAG_DMA_TX;
            /* configure low level device */
            serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *)RT_DEVICE_FLAG_DMA_TX);
        }
#endif /* RT_SERIAL_USING_DMA */
        else
        {
            serial->serial_tx = RT_NULL;
        }
    }
    else
    {
        if (oflag & RT_DEVICE_FLAG_INT_TX)
            dev->open_flag |= RT_DEVICE_FLAG_INT_TX;
#ifdef RT_SERIAL_USING_DMA
        else if (oflag & RT_DEVICE_FLAG_DMA_TX)
            dev->open_flag |= RT_DEVICE_FLAG_DMA_TX;
#endif /* RT_SERIAL_USING_DMA */
    }

    /* set stream flag */
    dev->open_flag |= stream_flag;

    return RT_EOK;
}

   乍一看,这函数写的实在是长,完全违背了不超过屏幕一页的规则。但是细看,会发现,这个函数也就做了以下几个事情:

       1. 先是检查打开方式与对应串口支持的方式是否匹配。若不匹配,则直接返回错误信息,若匹配,则继续后续操作。其中,流模式比较特殊,具体操作为不匹配,则不保存对应功能,原因稍后给出。

      2. 根据serial_rx这个指针是否被赋值,决定是否需要打开RX功能,其中,不同打开方式操作方式有细微差异

      3. 根据serial_tx这个指针是否被赋值,决定是否需要打开TX功能,其中,不同打开方式的操作方式有细微差异

       4. 存储流模式标记

串口读操作

/*
 * Serial poll routines
 */
rt_inline int _serial_poll_rx(struct rt_serial_device *serial, rt_uint8_t *data, int length)
{
    int ch;
    int size;

    RT_ASSERT(serial != RT_NULL);
    size = length;

    while (length)
    {
        ch = serial->ops->getc(serial);
        if (ch == -1) break;

        *data = ch;
        data ++; length --;

        if(serial->parent.open_flag & RT_DEVICE_FLAG_STREAM)
        {
            if (ch == '\n') break;
        }
    }

    return size - length;
}

/*
 * Serial DMA routines
 */
rt_inline int _serial_dma_rx(struct rt_serial_device *serial, rt_uint8_t *data, int length)
{
    rt_base_t level;

    RT_ASSERT((serial != RT_NULL) && (data != RT_NULL));

    level = rt_spin_lock_irqsave(&(serial->spinlock));

    if (serial->config.bufsz == 0)
    {
        int result = RT_EOK;
        struct rt_serial_rx_dma *rx_dma;

        rx_dma = (struct rt_serial_rx_dma*)serial->serial_rx;
        RT_ASSERT(rx_dma != RT_NULL);

        if (rx_dma->activated != RT_TRUE)
        {
            rx_dma->activated = RT_TRUE;
            RT_ASSERT(serial->ops->dma_transmit != RT_NULL);
            serial->ops->dma_transmit(serial, data, length, RT_SERIAL_DMA_RX);
        }
        else result = -RT_EBUSY;
        rt_spin_unlock_irqrestore(&(serial->spinlock), level);

        if (result == RT_EOK) return length;

        rt_set_errno(result);
        return 0;
    }
    else
    {
        struct rt_serial_rx_fifo *rx_fifo = (struct rt_serial_rx_fifo *) serial->serial_rx;
        rt_size_t recv_len = 0, fifo_recved_len = rt_dma_calc_recved_len(serial);

        RT_ASSERT(rx_fifo != RT_NULL);

        if (length < (int)fifo_recved_len)
            recv_len = length;
        else
            recv_len = fifo_recved_len;

        if (rx_fifo->get_index + recv_len < serial->config.bufsz)
            rt_memcpy(data, rx_fifo->buffer + rx_fifo->get_index, recv_len);
        else
        {
            rt_memcpy(data, rx_fifo->buffer + rx_fifo->get_index,
                    serial->config.bufsz - rx_fifo->get_index);
            rt_memcpy(data + serial->config.bufsz - rx_fifo->get_index, rx_fifo->buffer,
                    recv_len + rx_fifo->get_index - serial->config.bufsz);
        }
        rt_dma_recv_update_get_index(serial, recv_len);
        rt_spin_unlock_irqrestore(&(serial->spinlock), level);
        return recv_len;
    }
}

/*
 * Serial interrupt routines
 */
rt_inline int _serial_int_rx(struct rt_serial_device *serial, rt_uint8_t *data, int length)
{
    int size;
    struct rt_serial_rx_fifo* rx_fifo;

    RT_ASSERT(serial != RT_NULL);
    size = length;

    rx_fifo = (struct rt_serial_rx_fifo*) serial->serial_rx;
    RT_ASSERT(rx_fifo != RT_NULL);

    /* read from software FIFO */
    while (length)
    {
        int ch;
        rt_base_t level;

        /* disable interrupt */
        level = rt_spin_lock_irqsave(&(serial->spinlock));

        /* there's no data: */
        if ((rx_fifo->get_index == rx_fifo->put_index) && (rx_fifo->is_full == RT_FALSE))
        {
            /* no data, enable interrupt and break out */
            rt_spin_unlock_irqrestore(&(serial->spinlock), level);
            break;
        }

        /* otherwise there's the data: */
        ch = rx_fifo->buffer[rx_fifo->get_index];
        rx_fifo->get_index += 1;
        if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;

        if (rx_fifo->is_full == RT_TRUE)
        {
            rx_fifo->is_full = RT_FALSE;
        }

        /* enable interrupt */
        rt_spin_unlock_irqrestore(&(serial->spinlock), level);

        *data = ch & 0xff;
        data ++; length --;
    }

    return size - length;
}

static rt_ssize_t rt_serial_read(struct rt_device *dev,
                                rt_off_t          pos,
                                void             *buffer,
                                rt_size_t         size)
{
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    if (size == 0) return 0;

    serial = (struct rt_serial_device *)dev;

    if (dev->open_flag & RT_DEVICE_FLAG_INT_RX)
    {
        return _serial_int_rx(serial, (rt_uint8_t *)buffer, size);
    }
#ifdef RT_SERIAL_USING_DMA
    else if (dev->open_flag & RT_DEVICE_FLAG_DMA_RX)
    {
        return _serial_dma_rx(serial, (rt_uint8_t *)buffer, size);
    }
#endif /* RT_SERIAL_USING_DMA */

    return _serial_poll_rx(serial, (rt_uint8_t *)buffer, size);
}

   从这里面可以看到,串口框架实现了三种读取方式,三种读取方式的具体实现如下:

         1. 中断接收(_serial_int_rx):是用中断方式去驱动读取,而由于存在buffer操作,因此需要自旋锁的保护措施,以防多方操作buffer导致数据异常。

        2. dma接收(_serial_dma_rx):里面操作根据不同的dma类型,存在不同的操作。其分为两种实现,一种为dma自己维护buffer时,直接调用驱动提供的dma_transmit接口读取数据,另一种与中断方式类似,直接读取buffer中的缓存。两种读取方式都需要自旋锁保护(暂时不理解为何dma维护buffre时,也需要自旋锁保护)。

       3. 轮询接收(_serial_poll_rx):此方法使用轮询的方式直接从驱动中读取数据,因此实现中直接对接驱动中的getc,且没有自旋锁保护的措施。而在这实现中,可以发现,所谓的流模式,即为遇到换行符时退出读取。

串口写操作

rt_inline int _serial_poll_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{
    int size;
    RT_ASSERT(serial != RT_NULL);

    size = length;
    while (length)
    {
        /*
         * to be polite with serial console add a line feed
         * to the carriage return character
         */
        if (*data == '\n' && (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM))
        {
            serial->ops->putc(serial, '\r');
        }

        serial->ops->putc(serial, *data);

        ++ data;
        -- length;
    }

    return size - length;
}

rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{
    rt_base_t level;
    rt_err_t result;
    struct rt_serial_tx_dma *tx_dma;

    tx_dma = (struct rt_serial_tx_dma*)(serial->serial_tx);

    result = rt_data_queue_push(&(tx_dma->data_queue), data, length, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        level = rt_spin_lock_irqsave(&(serial->spinlock));
        if (tx_dma->activated != RT_TRUE)
        {
            tx_dma->activated = RT_TRUE;
            rt_spin_unlock_irqrestore(&(serial->spinlock), level);

            /* make a DMA transfer */
            serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);
        }
        else
        {
            rt_spin_unlock_irqrestore(&(serial->spinlock), level);
        }

        return length;
    }
    else
    {
        rt_set_errno(result);
        return 0;
    }
}

rt_inline int _serial_int_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{
    int size;
    struct rt_serial_tx_fifo *tx;

    RT_ASSERT(serial != RT_NULL);

    size = length;
    tx = (struct rt_serial_tx_fifo*) serial->serial_tx;
    RT_ASSERT(tx != RT_NULL);

    while (length)
    {
        /*
         * to be polite with serial console add a line feed
         * to the carriage return character
         */
        if (*data == '\n' && (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM))
        {
            if (serial->ops->putc(serial, '\r') == -1)
            {
                rt_completion_wait(&(tx->completion), RT_WAITING_FOREVER);
                continue;
            }
        }

        while (serial->ops->putc(serial, *(char*)data) == -1)
        {
            rt_completion_wait(&(tx->completion), RT_WAITING_FOREVER);
        }

        data ++; length --;
    }

    return size - length;
}

static rt_ssize_t rt_serial_write(struct rt_device *dev,
                                 rt_off_t          pos,
                                 const void       *buffer,
                                 rt_size_t         size)
{
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    if (size == 0) return 0;

    serial = (struct rt_serial_device *)dev;

    if (dev->open_flag & RT_DEVICE_FLAG_INT_TX)
    {
        return _serial_int_tx(serial, (const rt_uint8_t *)buffer, size);
    }
#ifdef RT_SERIAL_USING_DMA
    else if (dev->open_flag & RT_DEVICE_FLAG_DMA_TX)
    {
        return _serial_dma_tx(serial, (const rt_uint8_t *)buffer, size);
    }
#endif /* RT_SERIAL_USING_DMA */
    else
    {
        return _serial_poll_tx(serial, (const rt_uint8_t *)buffer, size);
    }
}

   串口写的函数结构,大致上和串口读一致,因此理解起来也是一样的思路,具体如下:

       1. 中断发送(_serial_int_tx):中断发送,其实实现很简单,就是调用驱动提供的putc接口,但是需要注意的是,需要收到驱动回复的完成量tx->completion信息再进行下一步的写。另外中断读不加流操作,中断写加流操作,这点完全看不懂。

      2. dma发送(_serial_dma_tx):DMA方式的写,其实实现不止于此,但是放在这全部写,又有些多余,因此这里仅写这部分的思路,这部分的思路为,先将所有数据存储至一个叫tx_dma->data_queue的队列中,然后调用dma_transmit去实现数据发送。而我提到的后续部分,实际上是因为需要有DMA发送完成信息,以及由于dma发送有长度限制,因此在接收到完成信息后需要判断是否还有数据要发送,是否需要告诉上层数据发送完毕。至于这部分,晚点贴出。

      3. 轮询发送(_serial_poll_tx):这个方法与中断传输一致,可能由于是轮询方式直接发送,不能像中断那样在中断产生前MCU干别的事,因此就没有加完成量的处理了。

串口控制操作

static rt_err_t rt_serial_control(struct rt_device *dev,
                                  int              cmd,
                                  void             *args)
{
    rt_err_t ret = RT_EOK;
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    serial = (struct rt_serial_device *)dev;

    switch (cmd)
    {
        case RT_DEVICE_CTRL_SUSPEND:
            /* suspend device */
            dev->flag |= RT_DEVICE_FLAG_SUSPENDED;
            break;

        case RT_DEVICE_CTRL_RESUME:
            /* resume device */
            dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED;
            break;

        case RT_DEVICE_CTRL_CONFIG:
            if (args)
            {
                struct serial_configure *pconfig = (struct serial_configure *) args;
                if (pconfig->bufsz != serial->config.bufsz && serial->parent.ref_count)
                {
                    /*can not change buffer size*/
                    return -RT_EBUSY;
                }
                /* set serial configure */
                serial->config = *pconfig;
                if (serial->parent.ref_count)
                {
                    /* serial device has been opened, to configure it */
                    serial->ops->configure(serial, (struct serial_configure *) args);
                }
            }
            break;
        case RT_DEVICE_CTRL_NOTIFY_SET:
            if (args)
            {
                rt_memcpy(&serial->rx_notify, args, sizeof(struct rt_device_notify));
            }
            break;

        case RT_DEVICE_CTRL_CONSOLE_OFLAG:
            if (args)
            {
                *(rt_uint16_t*)args = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM;
            }
            break;
#ifdef RT_USING_POSIX_STDIO
#if defined(RT_USING_POSIX_TERMIOS)
        case TCGETA:
        case TCGETS:
            {
                struct termios *tio, tmp;

                if (cmd == TCGETS)
                {
                    tio = (struct termios*)args;
                }
                else
                {
                    tio = &tmp;
                }

                if (tio == RT_NULL) return -RT_EINVAL;

                tio->c_iflag = 0;
                tio->c_oflag = 0;
                tio->c_lflag = 0;

                /* update oflag for console device */
                if (rt_console_get_device() == dev)
                    tio->c_oflag = OPOST | ONLCR;

                /* set cflag */
                tio->c_cflag = 0;
                if (serial->config.data_bits == DATA_BITS_5)
                    tio->c_cflag = CS5;
                else if (serial->config.data_bits == DATA_BITS_6)
                    tio->c_cflag = CS6;
                else if (serial->config.data_bits == DATA_BITS_7)
                    tio->c_cflag = CS7;
                else if (serial->config.data_bits == DATA_BITS_8)
                    tio->c_cflag = CS8;

                if (serial->config.stop_bits == STOP_BITS_2)
                    tio->c_cflag |= CSTOPB;

                if (serial->config.parity == PARITY_EVEN)
                    tio->c_cflag |= PARENB;
                else if (serial->config.parity == PARITY_ODD)
                    tio->c_cflag |= (PARODD | PARENB);

                cfsetospeed(tio, _get_speed(serial->config.baud_rate));

                if (cmd == TCGETA)
                {
                    _termios_to_termio(tio, args);
                }
            }
            break;
        case TCSETAW:
        case TCSETAF:
        case TCSETA:
        case TCSETSW:
        case TCSETSF:
        case TCSETS:
            {
                int baudrate;
                struct serial_configure config;
                struct termios *tio, tmp;

                if ((cmd >= TCSETA) && (cmd <= TCSETA + 2))
                {
                    _termio_to_termios(args, &tmp);
                    tio = &tmp;
                }
                else
                {
                    tio = (struct termios*)args;
                }

                if (tio == RT_NULL) return -RT_EINVAL;

                config = serial->config;
                baudrate = _get_baudrate(cfgetospeed(tio));
                config.baud_rate = baudrate;

                switch (tio->c_cflag & CSIZE)
                {
                case CS5:
                    config.data_bits = DATA_BITS_5;
                    break;
                case CS6:
                    config.data_bits = DATA_BITS_6;
                    break;
                case CS7:
                    config.data_bits = DATA_BITS_7;
                    break;
                default:
                    config.data_bits = DATA_BITS_8;
                    break;
                }

                if (tio->c_cflag & CSTOPB) config.stop_bits = STOP_BITS_2;
                else config.stop_bits = STOP_BITS_1;

                if (tio->c_cflag & PARENB)
                {
                    if (tio->c_cflag & PARODD) config.parity = PARITY_ODD;
                    else config.parity = PARITY_EVEN;
                }
                else config.parity = PARITY_NONE;

                serial->ops->configure(serial, &config);
            }
            break;
#ifndef RT_USING_TTY
        case TCFLSH:
            {
                int queue = (int)(rt_ubase_t)args;

                _tc_flush(serial, queue);
            }

            break;
        case TCXONC:
            break;
#endif /*RT_USING_TTY*/
#endif /*RT_USING_POSIX_TERMIOS*/
        case TIOCSWINSZ:
            {
                struct winsize* p_winsize;

                p_winsize = (struct winsize*)args;
                rt_kprintf("\x1b[8;%d;%dt", p_winsize->ws_col, p_winsize->ws_row);
            }
            break;
        case TIOCGWINSZ:
            {
                struct winsize* p_winsize;
                p_winsize = (struct winsize*)args;

                if(rt_thread_self() != rt_thread_find("tshell"))
                {
                    /* only can be used in tshell thread; otherwise, return default size */
                    p_winsize->ws_col = 80;
                    p_winsize->ws_row = 24;
                }
                else
                {
                    #include <shell.h>
                    #define _TIO_BUFLEN 20
                    char _tio_buf[_TIO_BUFLEN];
                    unsigned char cnt1, cnt2, cnt3, i;
                    char row_s[4], col_s[4];
                    char *p;

                    rt_memset(_tio_buf, 0, _TIO_BUFLEN);

                    /* send the command to terminal for getting the window size of the terminal */
                    rt_kprintf("\033[18t");

                    /* waiting for the response from the terminal */
                    i = 0;
                    while(i < _TIO_BUFLEN)
                    {
                        _tio_buf[i] = finsh_getchar();
                        if(_tio_buf[i] != 't')
                        {
                            i ++;
                        }
                        else
                        {
                            break;
                        }
                    }
                    if(i == _TIO_BUFLEN)
                    {
                        /* buffer overloaded, and return default size */
                        p_winsize->ws_col = 80;
                        p_winsize->ws_row = 24;
                        break;
                    }

                    /* interpreting data eg: "\033[8;1;15t" which means row is 1 and col is 15 (unit: size of ONE character) */
                    rt_memset(row_s,0,4);
                    rt_memset(col_s,0,4);
                    cnt1 = 0;
                    while(cnt1 < _TIO_BUFLEN && _tio_buf[cnt1] != ';')
                    {
                        cnt1++;
                    }
                    cnt2 = ++cnt1;
                    while(cnt2 < _TIO_BUFLEN && _tio_buf[cnt2] != ';')
                    {
                        cnt2++;
                    }
                    p = row_s;
                    while(cnt1 < cnt2)
                    {
                        *p++ = _tio_buf[cnt1++];
                    }
                    p = col_s;
                    cnt2++;
                    cnt3 = rt_strlen(_tio_buf) - 1;
                    while(cnt2 < cnt3)
                    {
                        *p++ = _tio_buf[cnt2++];
                    }

                    /* load the window size date */
                    p_winsize->ws_col = atoi(col_s);
                    p_winsize->ws_row = atoi(row_s);
                #undef _TIO_BUFLEN
                }

                p_winsize->ws_xpixel = 0;/* unused */
                p_winsize->ws_ypixel = 0;/* unused */
            }
            break;
        case FIONREAD:
            {
                rt_size_t recved = 0;
                rt_base_t level;

                level = rt_spin_lock_irqsave(&(serial->spinlock));
                recved = _serial_fifo_calc_recved_len(serial);
                rt_spin_unlock_irqrestore(&(serial->spinlock), level);

                *(rt_size_t *)args = recved;
            }
            break;
#endif /* RT_USING_POSIX_STDIO */
        default :
            /* control device */
            ret = serial->ops->control(serial, cmd, args);
            break;
    }

    return ret;
}

   看似控制操作很多,实际上去掉几个目前学习来说无用宏包裹的部分看, 仅仅有那么几个入口:

       1. 挂起/恢复串口:虽然写了这么个标志,但是没啥用,因为这标志压根没用到

      2. 设置串口参数入口:功能同init中,个人理解为打开串口前的操作入口,即find,设置串口参数,打开串口

3. RT_DEVICE_CTRL_NOTIFY_SET:不知道什么情况下需要使用这种方式上报信息,tx_complete完全可以满足要求

4. RT_DEVICE_CTRL_CONSOLE_OFLAG:不明所以的实现,或许是历史遗留吧,得梳理历史提交记录才能确认

5. 其他入口:标准框架实现不了,驱动独有的设置入口,对应接口为驱动层的control

串口关闭操作

static rt_err_t rt_serial_close(struct rt_device *dev)
{
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    serial = (struct rt_serial_device *)dev;

    /* this device has more reference count */
    if (dev->ref_count > 1) return RT_EOK;

    if (dev->open_flag & RT_DEVICE_FLAG_INT_RX)
    {
        struct rt_serial_rx_fifo* rx_fifo;

        /* configure low level device */
        serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void*)RT_DEVICE_FLAG_INT_RX);
        dev->open_flag &= ~RT_DEVICE_FLAG_INT_RX;

        rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;
        RT_ASSERT(rx_fifo != RT_NULL);

        rt_free(rx_fifo);
        serial->serial_rx = RT_NULL;

    }
#ifdef RT_SERIAL_USING_DMA
    else if (dev->open_flag & RT_DEVICE_FLAG_DMA_RX)
    {
        /* configure low level device */
        serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *) RT_DEVICE_FLAG_DMA_RX);
        dev->open_flag &= ~RT_DEVICE_FLAG_DMA_RX;

        if (serial->config.bufsz == 0)
        {
            struct rt_serial_rx_dma* rx_dma;

            rx_dma = (struct rt_serial_rx_dma*)serial->serial_rx;
            RT_ASSERT(rx_dma != RT_NULL);

            rt_free(rx_dma);
        }
        else
        {
            struct rt_serial_rx_fifo* rx_fifo;

            rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;
            RT_ASSERT(rx_fifo != RT_NULL);

            rt_free(rx_fifo);
        }
        serial->serial_rx = RT_NULL;

    }
#endif /* RT_SERIAL_USING_DMA */

    if (dev->open_flag & RT_DEVICE_FLAG_INT_TX)
    {
        struct rt_serial_tx_fifo* tx_fifo;

        serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void*)RT_DEVICE_FLAG_INT_TX);
        dev->open_flag &= ~RT_DEVICE_FLAG_INT_TX;

        tx_fifo = (struct rt_serial_tx_fifo*)serial->serial_tx;
        RT_ASSERT(tx_fifo != RT_NULL);

        rt_free(tx_fifo);
        serial->serial_tx = RT_NULL;

        /* configure low level device */
    }
#ifdef RT_SERIAL_USING_DMA
    else if (dev->open_flag & RT_DEVICE_FLAG_DMA_TX)
    {
        struct rt_serial_tx_dma* tx_dma;

        /* configure low level device */
        serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *) RT_DEVICE_FLAG_DMA_TX);
        dev->open_flag &= ~RT_DEVICE_FLAG_DMA_TX;

        tx_dma = (struct rt_serial_tx_dma*)serial->serial_tx;
        RT_ASSERT(tx_dma != RT_NULL);

        rt_data_queue_deinit(&(tx_dma->data_queue));

        rt_free(tx_dma);
        serial->serial_tx = RT_NULL;

    }
#endif /* RT_SERIAL_USING_DMA */

    serial->ops->control(serial, RT_DEVICE_CTRL_CLOSE, RT_NULL);
    dev->flag &= ~RT_DEVICE_FLAG_ACTIVATED;

    return RT_EOK;
}

   关闭操作,就是关闭硬件,销毁资源,而从实际代码上看,这入口也就干了这么个事。但是需要注意的是,他只有在所有打开这个串口的应用都关闭了串口后才去操作。

Add on

    看了那么多,实际上我们漏了一个内容,接收时,数据是怎么送到buffer里的,发送时,完成量是怎么发出的,这一系列功能,貌似都没有在上面的分析中体现。而这部分代码,实际上在serial.c里面有写,具体实现如下:

/* ISR for serial interrupt */
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
    switch (event & 0xff)
    {
        case RT_SERIAL_EVENT_RX_IND:
        {
            int ch = -1;
            rt_base_t level;
            struct rt_serial_rx_fifo* rx_fifo;

            /* interrupt mode receive */
            rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;
            RT_ASSERT(rx_fifo != RT_NULL);

            while (1)
            {
                ch = serial->ops->getc(serial);
                if (ch == -1) break;


                /* disable interrupt */
                level = rt_spin_lock_irqsave(&(serial->spinlock));

                rx_fifo->buffer[rx_fifo->put_index] = ch;
                rx_fifo->put_index += 1;
                if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;

                /* if the next position is read index, discard this 'read char' */
                if (rx_fifo->put_index == rx_fifo->get_index)
                {
                    rx_fifo->get_index += 1;
                    rx_fifo->is_full = RT_TRUE;
                    if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;

                    _serial_check_buffer_size();
                }

                /* enable interrupt */
                rt_spin_unlock_irqrestore(&(serial->spinlock), level);
            }

            /**
             * Invoke callback.
             * First try notify if any, and if notify is existed, rx_indicate()
             * is not callback. This separate the priority and makes the reuse
             * of same serial device reasonable for RT console.
             */
            if (serial->rx_notify.notify)
            {
                serial->rx_notify.notify(serial->rx_notify.dev);
            }
            else if (serial->parent.rx_indicate != RT_NULL)
            {
                rt_size_t rx_length;

                /* get rx length */
                level = rt_spin_lock_irqsave(&(serial->spinlock));
                rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):
                    (serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));
                rt_spin_unlock_irqrestore(&(serial->spinlock), level);

                if (rx_length)
                {
                    serial->parent.rx_indicate(&serial->parent, rx_length);
                }
            }
            break;
        }
        case RT_SERIAL_EVENT_TX_DONE:
        {
            struct rt_serial_tx_fifo* tx_fifo;

            tx_fifo = (struct rt_serial_tx_fifo*)serial->serial_tx;
            rt_completion_done(&(tx_fifo->completion));
            break;
        }
#ifdef RT_SERIAL_USING_DMA
        case RT_SERIAL_EVENT_TX_DMADONE:
        {
            const void *data_ptr;
            rt_size_t data_size;
            const void *last_data_ptr;
            struct rt_serial_tx_dma *tx_dma;

            tx_dma = (struct rt_serial_tx_dma*) serial->serial_tx;

            rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);
            if (rt_data_queue_peek(&(tx_dma->data_queue), &data_ptr, &data_size) == RT_EOK)
            {
                /* transmit next data node */
                tx_dma->activated = RT_TRUE;
                serial->ops->dma_transmit(serial, (rt_uint8_t *)data_ptr, data_size, RT_SERIAL_DMA_TX);
            }
            else
            {
                tx_dma->activated = RT_FALSE;
            }

            /* invoke callback */
            if (serial->parent.tx_complete != RT_NULL)
            {
                serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);
            }
            break;
        }
        case RT_SERIAL_EVENT_RX_DMADONE:
        {
            int length;
            rt_base_t level;

            /* get DMA rx length */
            length = (event & (~0xff)) >> 8;

            if (serial->config.bufsz == 0)
            {
                struct rt_serial_rx_dma* rx_dma;

                rx_dma = (struct rt_serial_rx_dma*) serial->serial_rx;
                RT_ASSERT(rx_dma != RT_NULL);

                RT_ASSERT(serial->parent.rx_indicate != RT_NULL);
                serial->parent.rx_indicate(&(serial->parent), length);
                rx_dma->activated = RT_FALSE;
            }
            else
            {
                /* disable interrupt */
                level = rt_spin_lock_irqsave(&(serial->spinlock));
                /* update fifo put index */
                rt_dma_recv_update_put_index(serial, length);
                /* calculate received total length */
                length = rt_dma_calc_recved_len(serial);
                /* enable interrupt */
                rt_spin_unlock_irqrestore(&(serial->spinlock), level);
                /* invoke callback */
                if (serial->parent.rx_indicate != RT_NULL)
                {
                    serial->parent.rx_indicate(&(serial->parent), length);
                }
            }
            break;
        }
#endif /* RT_SERIAL_USING_DMA */
    }
}

     这个函数,需要在驱动函数对应的中断中去调用,以便满足串口框架上的各种实现。

总结

   至此,最初版本的串口框架已经分析完毕,而分析完这个框架,我们可以以以下方式实现串口驱动的适配,通过在驱动中构建 struct rt_serial_device,调用 rt_hw_serial_register实现设备注册,在不同的中断中调用rt_hw_serial_isr并给予不同参数实现数据处理的方式实现串口适配。






关键词: rtthread     串口     框架     v1    

专家
2025-08-04 21:11:08     打赏
2楼

感谢分享


专家
2025-08-04 21:15:43     打赏
3楼

感谢分享


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
睾丸痛什么原因 好景不长是什么意思 糖尿病适合喝什么饮料 你代表什么意思 贫血检查查什么项目
姨妈的老公叫什么 膀胱钙化是什么意思 更年期是什么 脾胃不好吃什么食物可以调理 本意是什么意思
吃什么补羊水最快 人为什么会晕车 1984年属鼠五行属什么 ltp什么意思 子宫内膜增生是什么原因
鳄鱼属于什么动物 螨虫长什么样子 d代表什么单位 重庆为什么叫重庆 6月22日是什么星座
婴儿放屁多是什么原因hcv8jop0ns5r.cn 什么人不能吃阿胶hcv8jop2ns6r.cn 健康管理是什么专业hcv8jop7ns4r.cn 感冒是什么症状hcv8jop6ns9r.cn 什么是吸附性义齿520myf.com
经常放屁是什么原因hcv8jop3ns3r.cn 11月20是什么星座hcv7jop9ns4r.cn 师长相当于地方什么级别inbungee.com 螨虫是什么wmyky.com 流鼻血吃什么好hcv9jop1ns0r.cn
虾米是什么意思liaochangning.com 大小周是什么意思hcv7jop9ns2r.cn 明天有什么考试hcv8jop7ns1r.cn 玉米须有什么作用hcv9jop5ns8r.cn 生花生吃了有什么好处hcv9jop3ns9r.cn
后羿射日什么意思hcv7jop6ns9r.cn 梦见买袜子是什么意思hcv9jop0ns6r.cn 八格牙路是什么意思hkuteam.com 什么叫甲沟炎hcv8jop2ns9r.cn 孱弱是什么意思travellingsim.com
百度