FIFO模式下外设控制器驱动开发-中断篇

1.简介

        驱动软件开发,就好比做菜,通过不同的烹饪方式,来做出满汉全席。

第一种方法就是用最简单的食材(GPIO),通过最高端的烹饪方式(模拟),将最鲜,最原始的味道(时序)来刺激我们的味蕾。

第二种方法则是往往使用丰富的食材,调料,添加到炒锅(高集成化的硬件控制器)中,通过最简单的烹饪方式(一件启动)完成美食的制作。

因此,上述的两种方法,在嵌入式驱动软件开发中,我们称之为GPIO模拟和硬件驱动控制器实现。

1.1 GPIO模拟和硬件控制器区别

(1)GPIO模拟是通过输出高低电平的时间,检测电平值和电平值的持续时间来实现例如UART,SPI,IIC等通信协议的时序,进而与其他外设通信。

(2)硬件控制器可以理解为更完备的GPIO模拟,是一种高度集成化的硬件电路。将UART,SPI,IIC的时序逻辑通过硬件实现。而软件只需要对其所在的控制器进行配置,从而达到与其他外设通信的目的。

两者的利弊:

GPIO模拟:

优势劣势
通用性:可以模拟任意协议时序时序准确性受CPU运行速率影响(延时,执行指令)
学习性:易于理解和掌握不同的协议时序输入/输出频率受限(高频设备无法驱动)
时序模拟需严格按照协议要求进行编码,软件设计复杂

硬件驱动控制器:

优势劣势
准确性:输入输出时序不受CPU影响,时序准确不能直观的理解时序的控制逻辑
丰富性:不同的配置参数,可以配置不同的逻辑功能单一性:每种控制器对应一种外设
完备性:协议逻辑完整,可应对不同的应用场景
易用性:软件编码简单,只需对控制器进行配置
封装性:集成度高,软件干预少

1.2 基本概念

        由于控制器的优势所在,实际产品中,驱动软件开发主要基于硬件控制器来实现。而GPIO模拟更多的是作为辅助调试手段。因此本文只是针对控制器和本文的主要内容做阐述。

以下关键字的概念,需结合对应的数据手册作区分,这里只做参考和文章撰写。

(1)FIFO:(First-In-First-Out)先进先出,是一种数据结构或存储结构。数据的输入输出需遵循先入先出的原则。
(2)TX_TL:(Transmit FIFO Threshold Level)传输FIFO阈值数量,指传输FIFO中,已发送数量等于或少于该值时,触发对应事件。

(3)RX_TL:(Receive FIFO Threshold Level)接收FIFO阈值数量,指接收FIFO中,已接收数量等于或大于该值时,出发对应事件。

(4)TXFLR:(Transmit FIFO Level)传输FIFO数量,指传输FIFO中,存放的有效数据数量。

(5)RXFLR:(Receive FIFO Level)接收FIFO数量,指接收FIFO中,已接收的有效数据数量。

(6)控制器中断:指的是不同的控制器,在发送或接收过程中,产生相对应的中断事件。例如TX_EMPTY(发送阈值中断),RX_FULL(接收阈值中断),ABORT(传输终止)等。

2. FIFO逻辑

        在控制器中(数字电路),FIFO缓冲区作为跨时钟域的数据传输,确保数据同步。

即:

(1)软件通过CPU的工作时钟对FIFO进行数据读写

(2)外设控制器通过器对应的工作时钟对FIFO进行数据读写

2.1 FIFO术语

(1)width(数据位宽):一般为8,16,32,64,......;其中,特殊的外设控制器可以配置实际的传输位数,例如SPI。

(2)depth(深度):指的是FIFO容量,一次性可以存放的数据个数,一般为8,16,32。

2.2 FIFO形式

如上述的图过程:

根据FIFO的depth,将一定数量的数据填充到FIFO中,当有读请求过程时,会将先写入的数据读出,之后如果有数据需要写入,则向后填写。

2.3 FIFO数量处理

        关于FIFO内部数量,需要结合其depth,考虑以下几个问题:

(1)数据是否溢出?

(2)何时进行数据的读写?

(3)TX_TL、RX_TL如何设置?

以FIFO的depth=8为例:

(1)关于数据是否会溢出,关键在于FIFO当前可写入数量。

A.第一种情况:显而易见,如果单次传输的数量大于8,则数据溢出。

B.第二种情况:中断事件中,如果FIFO中存在数据未发送完毕,一次性写入数量大于8,数据也会溢出。

(2)数据的读写,包括主动查询和被动触发两种方式:

A.主动查询:通过查询FIFO状态(FIFO是否为空,FIFO是否已满,FIFO中的有效数据量等)来进行数据的收发。

B.被动触发:结合TX_TL、RX_TL触发中断事件(TX_EMPTY,RX_FULL),来进行后续数据的收发处理。

(3)TX_TL、RX_TL由两个因素决定:

A.数据量:指传输个数远超过FIFO深度

B.单次传输的个数:指一次性读写FIFO的个数,目的是提高传输效率

接下来,以具体的过程来描述TX_TL、RX_TL和FIFO之间的逻辑关系:

发送过程:假定TX_TL配置为4,总共发送7个数据

过程如下:

(1)向FIFO写入4个数据,等待读请求。

(2)数据读完毕后,触发TX_EMPTY事件.

(3)继续向FIFO写入3个数据,等待发送完毕,再次触发TX_EMPTY事件。

(4)程序判断是否结束。

接收过程:假定RX_TL配置为2,总共接收5个数据

过程如下:

(1)当外部写入RX FIFO 2个数据时,触发RX_FULL事件。

(2)程序读取数据,并读空FIFO。

(3)外部再次写入3个数据,触发RX_FULL事件。

(4)程序继续读取数据并判断是否结束。

需要注意的是:接收到的数据个数需大于等于RX_TL,才能触发RX_FULL事件。

3. FIFO模式下的中断处理

        前面提到,无论是发送还是接收模式,再设定的TX_TL、RX_TL条件下,会触发对应的事件。而这个事件在微控制器系统中,一般指的是中断。驱动软件中,一旦开启了控制器对应的中断,当触发中断事件时,则需要根据配置的参数做对应的处理。

接下来,就发送,接收,发送接收模式做相关的逻辑说明。

3.1 发送模式

        该模式下的TX_EMPTY中断服务程序,需要根据TX_TL,FIFO状态,数量做逻辑处理。包括两种设计思路:

(1)当触发TX_EMPTY中断时,根据TX_TL,FIFO深度以及发送数量做处理。步骤如下:

A.计算剩余发送个数

B.计算填写FIFO的个数(根据TX_TL depth),需考虑是否溢出

C.循环写入FIFO

D.等待下一次中断

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//变量
static unsigned int tx_total_cnt;
static volatile unsigned int tx_cnt = 0;
static unsigned char *tx_data;

//发送函数
void xxx_send(const void *tx, unsigned int num)
{
    //初始化数据
    tx_data = (unsigned char*)tx;
    tx_total_cnt = num;
    tx_cnt = 0;

    //使能中断

    //启动控制器发送
}

void isr_tx_handle()
{
    unsigned int i = 0, tx_remain = 0, w_cnt;
   
    //判断数据是否发送完毕
    if(tx_cnt == tx_total_cnt)
    {
        //do something
        return;
    }
     
    //计算剩余发送个数
    tx_remain = tx_total_cnt - tx_cnt;
    
    //计算写入FIFO个数,通过 深度-阈值,避免溢出
    w_cnt = (tx_remain > (depth - TX_TL))?(depth - TX_TL):tx_remain;

    //循环写入FIFO
    for(i=0; i<w_cnt; i++)
    {
        //假定FIFO寄存器为DR
        DR = *data++;
        tx_cnt++;
    }

}

(2)当触发TX_EMPTY中断时,根据FIFO状态和数量做处理。步骤如下:

A.判断数据是否发送完成

B.判断FIFO是否写满

C.循环写入FIFO

D.等待下一次中断

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

//变量
static unsigned int tx_total_cnt;
static volatile unsigned int tx_cnt = 0;
static unsigned char *tx_data;

//发送函数
void xxx_send(const void *tx, unsigned int num)
{
    //初始化数据
    tx_data = (unsigned char*)tx;
    tx_total_cnt = num;
    tx_cnt = 0;

    //使能中断

    //启动控制器发送
}

//FIFO是否未满
bool xxx_tx_full_is_not_full()
{
    //假定FIFO状态寄存器为SR,tx full位于bit3
    if((SR & 0x8) == 0)
        return false;

    return true;
}

void isr_tx_handle()
{
   
    //判断数据是否发送完毕
    if(tx_cnt == tx_total_cnt)
    {
        //do something
        return;
    }
     
    //判断FIFO状态和已发送个数
    while((tx_cnt < tx_total_cnt) && (xxx_tx_full_is_not_full))
    {
        //循环写入FIFO
        //假定FIFO寄存器为DR
        DR = *data++;
        tx_cnt++;
    }

}

两种设计思路差异:

第一种:可以由软件直接控制FIFO个数的写入和中断触发的次数,但是需要准确控制写入的个数

第二种:无需关心写入FIFO的数量,有空间则写入;但该方式不能直观的看到中断触发的次数,且占用CPU的时间相对较长。

3.2 接收模式

        该模式下的RX_FULL中断服务程序,需要根据RX_TL,RXFLR,数量做逻辑处理。步骤如下:

(1)根据实际的单次传输数量设定RX_TL值

(2)等待RX_RULL中断

(3)结合RXFLR和数量读取数据

(4)程序判断是否结束

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

//变量
static unsigned int rx_total_cnt;
static volatile unsigned int rx_cnt = 0;
static unsigned char *rx_data;

//发送函数
void xxx_recv(void *rx, unsigned int num)
{
    //初始化数据
    rx_data = (unsigned char*)rx;
    rx_total_cnt = num;
    rx_cnt = 0;    

    //使能中断

    //启动控制器接收
}

void isr_rx_handle()
{
   
    //判断数据是否接收完毕
    if(rx_cnt == rx_total_cnt)
    {
        //do something
        return;
    }
     
    //判断FIFO状态和已接收个数
    while((rx_cnt < rx_total_cnt) && (RXFLR > 0))
    {
        //循环读取FIFO
        //假定FIFO寄存器为DR
        *data++ = DR;
        rx_cnt++;
    }

}

备注:如果控制器没有RXFLR,则可以根据RX_TL的进行数据读取,类似发送模式的方法。

3.3 发送接收模式

        该模式主要应用于全双工的处理逻辑,会同时存在发送和接收两种,则需要根据TX_TL,RX_TL,FIFO状态,RXFLR,数量等多个参数进行处理,即需要结合发送和接收两种模式。步骤如下:

(1)等待TX_EMPTY中断触发

(2)判断发送的数据个数,写入TX FIFO

(3)等待RX_FULL中断触发

(4)读取RX FIFO数据

(5)判断程序是否结束

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

//变量
//发送
static unsigned int tx_total_cnt;
static volatile unsigned int tx_cnt = 0;
static unsigned char *tx_data;

//接收
static unsigned int rx_total_cnt;
static volatile unsigned int rx_cnt = 0;
static unsigned char *rx_data;

//发送函数
void xxx_transfer(const void *tx, void *rx, unsigned int num)
{
    //初始化数据
    tx_data = (unsigned char *)tx;
    tx_total_cnt = num;
    tx_cnt = 0;

    rx_data = (unsigned char *)rx;
    rx_total_cnt = num;
    rx_cnt = 0;   

    //使能中断

    //启动控制器接收
}

//先发后收
void isr_rx_transfer()
{
   
    //判断数据是否接收完毕,因接收和发送的数量应该保持一致,这里判断接收即可
    if(rx_cnt == rx_total_cnt)
    {
        //do something
        return;
    }
    
    //RX_FULL中断 判断FIFO状态和已接收个数
    while((rx_cnt < rx_total_cnt) && (RXFLR > 0))
    {
        //循环读取FIFO
        //假定FIFO寄存器为DR
        *data++ = DR;
        rx_cnt++;
    }

    //TX_EMPTY中断,填写FIFO
    unsigned int i = 0, tx_remain = 0, w_cnt;
       
    //计算剩余发送个数
    tx_remain = tx_total_cnt - tx_cnt;
    
    //计算写入FIFO个数,通过 深度-阈值,避免溢出
    w_cnt = (tx_remain > (depth - TX_TL))?(depth - TX_TL):tx_remain;

    //循环写入FIFO
    for(i=0; i<w_cnt; i++)
    {
        //假定FIFO寄存器为DR
        DR = *data++;
        tx_cnt++;
    }   

}

4.总结

        本文主要介绍的是在FIFO模式下的中断处理逻辑和思维方式。在实际开发过程中,不同的驱动控制器的设计及其它的复杂性都有所不同,因此,我们需要结合其对应的databook进行开发调试。不过,值得肯定的是,其中最基本的原理都是相通的。望驱动软件之美,你我他皆知!!

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K成长日志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值