[交流][拍砖]新版驱动接口风格调查(二):DMA
本帖最后由 Gorgon_Meducer 于 2013-11-13 17:37 编辑说在前面的话
感谢上次大家关于IO口配置的踊跃发言,使我们能够远离闭门造车和自我感觉良好的状态,如大家所见,现在两个帖子
都做了更新,根据大家的意见修正了风格。这次展示的是DMA的接口风格,希望大家踊跃拍砖。
说起DMA,本质来说就是替代内核去完成 存储器之间,存储器与外设之间,外设与外设之间的数据搬运。这里面涉及到
地址的问题,数据大小的问题,大小端的问题,等等原本简单的工作一下就复杂起来了。我们试图在两个层面上提供一个比
较容易使用的接口:从灵活性上说,如果你是DMA的使用高手,那么我们的接口不应该限制你的发挥;如果你是DMA的普通
使用者,虽然不太熟悉,但是对这种“高上大”的“新事物”还是有几分向往的,我们希望为你们提供套餐式的服务,比如
后面例子展示的4种常见套餐:存储器到存储器,外设到存储器,存储器到外设和外设到外设。
另外,得益于DMA的强大,它提供一种叫做链表的模式,就是DMA的传输控制是像普通节点一样保存在SRAM里的,这个
节点实际上是一个结构体,结构体有一个NEXT指针,也就是说DMA允许你把一系列想做的操作都通过这样一个链表记录下来
然后他会勤勤恳恳的替你一个一个的完成而基本不用干预——想到能做什么了吧?是的,DMA甚至可以帮你完成12864的数据
刷新,你只要在SRAM里面保存一个显存,然后你用链表描述好IO操作的过程,剩下就什么都不用管了,是不是很方便,很强
大?一般来说,普通外设的寄存器设置和数据搬运都不在话下。
说了这么多,主要还是想让大家从如何简化DMA操作的接口出发来吐槽。来吧,让搬砖拍的更猛烈吧!
这是新版本编码规范的一个范例代码,包含对应的驱动模型和接口规范,我希望在不提供进一步解说的情况下听听大家
的意见,并提供以下信息:
1、第一眼给你的感受,是喜欢 恐惧 还是 茫然无措
2、顺次阅读代码后,代码要表达的意思你是否已经了解大概,表意是否清晰
3、有什么你觉得疑惑的地方?
4、有什么改进意见?
5、任何批评意见都是非常欢迎的
如果可能,希望大家能在回帖中描述下你理解的代码的行为。
这个库的目标就是要让代码使用起来简单,不仅仅要屏蔽底层的寄存器操作,还要做到功能和意义一目了然。
参与的人,即便自觉是菜鸟,也不用觉得自己水平不够之类的,因为你们就是最终的用户,你们是最有发言权的!
非常感谢大家的参与。
P.S: 这个库将是未来一个实质性的半导体产品的系统库。
范例一:Memory 到 Memory的数据搬运
static volatile uint8_t chSource[]= { //!< 测试用的数据源 source data
MREPEAT(TEST_COUNT, SOURCE_DATA, 0)
};
static volatile uint8_t chTarget[] = { //!< 测试用的目标地址 destination data
MREPEAT(TEST_COUNT, TARGET_DATA, 0)
};
DMA_CHN_CFG(
DMA_CHANNEL0, //!< channel 0
DMA_TSF_CFG(
DMA_MANUAL_TRIGGER_NEXT, //!< 手动触发 Manual Trigger next block
DMA_MODE_MEMORY_TO_MEMORY, //!< 使用Memory到Memory传输的模板 momory to momory mode
DMA_DATA_BITS_8, //!< 要传输数据的数据类型 data bits size
UBOUND(chSource), //!< 要传输多少个数据 data size
&chSource, //!< 源数据地址 source address
&chTarget //!< 目标地址 destination address
),
);
DMA0.Trigger(DMA_CHANNEL0_MASK); //!< 触发传输 set trigger
//! 等待传输完成
while (!DMA_IS_CHN_TSF_CPL(DMA_CHANNEL0));
范例二:外设到 存储器 的数据搬运
static volatile uint32_t wTarget; //!< 目标缓冲区 destination data
DMA_CHN_CFG(
DMA_CHANNEL1, //!< channel 1
DMA_TSF_CFG(
DMA_MANUAL_TRIGGER_NEXT, //!< 手动触发模式 Manual Trigger next block
DMA_MODE_PERIPHRAL_TO_MEMORY, //!< 使用从外设搬运数据到存储器的模板 peripheral to memory mode
DMA_DATA_BITS_32, //!< 数据类型 data bits size
TEST_COUNT, //!< 要传输多少个数据 data size
&XXX_USART0.RBR_THR_DLL.RBR.Value, //!< 源寄存器的地址 source address
&wTarget //!< 目标缓冲区的地址 destination address
),
);
DMA0.Trigger(DMA_CHANNEL1_MASK); //!< set trigger
while (DMA0.GetChannelState(DMA_CHANNEL1, STATE_TIRG));
范例五:单通道链表模式
static volatile uint32_t wMemory0[] @0x20004200ul = { //!< source of first
MREPEAT(TEST_COUNT, MEMORY0_DATA, 0)
};
static volatile uint32_t wMemory1[] @0x20004300ul = { //!< destination of first and source of second
MREPEAT(TEST_COUNT, MEMORY1_DATA, 0)
};
static volatile uint32_t wMemory2[] @0x20004400ul = { //!< destination of second
MREPEAT(TEST_COUNT, MEMORY2_DATA, 0)
};
DMA_CHN_CFG(
DMA_CHANNEL0, //!< channel 0
DMA_TSF_LIST(
TSF_ITEM (
DMA_MODE_MEMORY_TO_MEMORY | //!< memory to memory mode
DMA_DATA_BITS_32, //!< data bits size
TEST_COUNT, //!< data size
&wMemory0, //!< source address
&wMemory1 //!< destination address
),
TSF_ITEM (
DMA_MODE_MEMORY_TO_PERIPHRAL | //!< memory to peripheral mode
DMA_DATA_BITS_32, //!< data bits size
TEST_COUNT, //!< data size
&wMemory1, //!< source address
&GSP_USART0.RBR_THR_DLL.THR.Value //!< destination address
),
TSF_ITEM (
DMA_MODE_PERIPHRAL_TO_PERIPHRAL | //!< peripheral to peripheral mode
DMA_DATA_BITS_32, //!< data bits size
TEST_COUNT, //!< data size
&GSP_USART1.RBR_THR_DLL.RBR.Value, //!< source address
&GSP_USART1.RBR_THR_DLL.THR.Value //!< destination address
),
TSF_ITEM (
DMA_MANUAL_TRIGGER_NEXT | //!< Manual Trigger next block
DMA_MODE_PERIPHRAL_TO_MEMORY | //!< peripheral to memory mode
DMA_DATA_BITS_32, //!< data bits size
TEST_COUNT, //!< data size
&GSP_USART0.RBR_THR_DLL.RBR.Value, //!< source address
&wMemory2 //!< destination address
)
)
);
DMA0.Trigger(DMA_CHANNEL0_MASK); //!< set trigger
delay(0x120); //!< wait usart0 send complete
DMA0.Trigger(DMA_CHANNEL0_MASK); //!< set trigger
delay(0x120); //!< wait usart1 send complete
DMA0.Trigger(DMA_CHANNEL0_MASK); //!< set trigger
本帖最后由 Gorgon_Meducer 于 2013-12-31 11:17 编辑
Sensor Hub 硬件触发的例子
应用场景描述:
我们来用DMA配合信号系统来构建一个芯片梦游的例子。是的,梦游,整个过程中,CPU都处于休眠状态。
应用是这个样子的:
a. CPU完成最初DMA和信号网络的配置以后就进入休眠模式并不再唤醒。
b. 假设外部有一个传感器,该传感器通过SPI从机模式与芯片通信,当传感器完成数据采样后,会拉高信号
线EXT_REQ(熟悉手机内部应用的人都很清楚这种结构,一般也把这根信号叫做中断信号) 。
c. 芯片在EXT_REQ的上升沿回复系统时钟,但并不唤醒内核,随后该信号触发DMA完成一次数据传送,
用以开启SPI的时钟。
d. 当SPI时钟开启后,由于发送缓冲中(TX_FIFO)预先填充了7个字节的数据,通信立即开始。
e. 在SPI通信的过程中,由于接受缓冲不为空,RX_FIFO_DAVL(RX FIFO Data Available)信号为高,该
信号持续触发DMA及时的将SPI接受缓冲中的数据搬运到指定的SRAM缓冲区内
f. 当SPI通信结束后,帧结束信号FRM_EXCHANGE将触发 DMA将 SPI的时钟关闭,并在关闭时钟后,预先
填充下一次通讯所需的7个字节的数据到SPI的发送缓冲中。由于SPI时钟已经关闭,FIFO仍然能操作,
但是数据却要等到下一次时钟恢复时才能发送了。
g. 当所有外设完成工作后,系统重新进入休眠模式。
可以看到,整个过程中内核并未参与。如果不需要数据处理,芯片只是采集传感器信息,集中后,以固定
的频率(比如100Hz)发送给主控芯片,那么仍然不需要涉及到内核。如果需要简单的数据处理,那么内核
可以在必要的时候唤醒,快速完成功能后重新进入休眠。这种思想,就是我在另外一个帖子里面提到的脉冲
工作模式。
void dma_cfg(void)
{
static uint32_t wSrc0 @0x20008004 = 0;
static uint32_t wSrc1 @0x20008008 = 1;
static uint32_t wOutputBuffer @0x2000800C = {0xAC,0,1,2,3,4,5};
static uint32_t wInputBuffer @0x20008104 ;
//!< init dma
DMA_CHN_CFG(
DMA_CHANNEL0, //!< channel0
DMA_CHN_TRIG_RISING_EDGE_SENSITIVE,
//!Open SPI0 P_CLK
DMA_TSF_CFG(
DMA_TSF_MODE_MEMORY_TO_PERIPHRAL |
DMA_TSF_MANUAL_TRIGGER_NEXT |
DMA_TSF_DATA_BITS_32 ,
1 ,
&wSrc1 ,
&GSP_CMC.PCLKDIV.Value
),
);
DMA_CHN_CFG(
DMA_CHANNEL1, //!< channel1
DMA_CHN_TRIG_RISING_EDGE_SENSITIVE |
DMA_CHN_BURST_TRANSFER |
DMA_CHN_BURST_SIZE_1 ,
//! read spi data CMD(Ignored) + 6 Bytes
DMA_TSF_CFG(
DMA_TSF_MODE_PERIPHRAL_TO_MEMORY |
DMA_TSF_MANUAL_TRIGGER_NEXT |
DMA_TSF_DATA_BITS_32 ,
7 ,
&GSP_SPI0.DR.Value ,
wInputBuffer
),
);
//!< init dma
DMA_CHN_CFG(
DMA_CHANNEL2, //!< channel2
DMA_CHN_TRIG_RISING_EDGE_SENSITIVE ,
//! disable SPI P_CLK
DMA_TSF_CFG(
DMA_TSF_MODE_MEMORY_TO_PERIPHRAL |
DMA_TSF_AUTO_TRIGGER_NEXT |
DMA_TSF_DATA_BITS_32 ,
1 ,
&wSrc0 ,
&GSP_CMC.PCLKDIV.Value
),
//! fill spi fifo with CMD + 6 Bytes(Dummy write for spi read)
DMA_TSF_CFG(
DMA_TSF_MODE_MEMORY_TO_PERIPHRAL |
DMA_TSF_MANUAL_TRIGGER_NEXT |
DMA_TSF_DATA_BITS_32 ,
7 ,
wOutputBuffer ,
&GSP_SPI0.DR.Value
),
);
}
void spi_dma_test_init(void)
{
...
// enable dma
PM_AHB_CLK_ENABLE(AHBCLK_DMA);
IO_CFG(
{PA7, IO_WORKS_AS_APIO0, IO_PULL_UP},
{PA1, IO_WORKS_AS_SPI0_SCK, IO_PULL_UP},
{PA2, IO_WORKS_AS_SPI0_MISO, IO_PULL_UP},
{PA3, IO_WORKS_AS_SPI0_MOSI, IO_PULL_UP},
{PA4, IO_WORKS_AS_SPI0_CS, IO_PULL_UP},
);
SPI_CFG(
SPI0,
SPI_MODE_MASTER |
SPI_MODE_FORMAT_SPI |
SPI_MODE_CLK_IDLE_LOW |
SPI_MODE_SAMP_SECOND_EDGE,
SPI_MODE_DATASIZE_8,
SPI_PCLK_DIV_1
);
SPI0.Open(); //!< enable spi0
PM_PCLK_SET(PCLK_SPI0,0); //!< disable P_CLK for SPI manually
//! fill spi fifo with CMD + 6 Bytes(Dummy write for spi read)
SPI0.Write(0xAC);
SPI0.Write(0x00);
SPI0.Write(0x01);
SPI0.Write(0x02);
SPI0.Write(0x03);
SPI0.Write(0x04);
SPI0.Write(0x05);
//! signal connection
SIGNAL_CFG(
/*----------------------------------------------------------------------------------------------*
* SIGNAL Source Signal Usage Digtal Filter *
*----------------------------------------------------------------------------------------------*/
/*! \note Use the raising edge of APIO0 to trigger DMA Channel0, the APIO0 is connected to
* PA7 in IO_CFG(). It is the trigger signal called EXT_REQ in the wave form.
*/
{ SIGNAL0,USE_SIGNAL_APIO0, TO_TRIGGER_DMA0_CHN0, ON_RAISING_EDGE },
/*! \note When there are any data available in SPI0 RX FIFO (signal gose high), general pulse
* to trigger DMA channel2 to fetch data from spi. Here EVERY_5_TIMES is the pulses
* prescaler, which means the pulse frequency is the 1/5 of the system clock.
*/
{ SIGNAL1,USE_SPI0_RX_FIFO_DAVL,TO_TRIGGER_DMA0_CHN1, ON_HIGH_LEVEL | EVERY_5_TIMES },
/*! \note When SPI Frame complete, trigger the dma channel3 transfer
*/
{ SIGNAL2,USE_SPI0_FRM_EXCHANGED, TO_TRIGGER_DMA0_CHN2, },
);
//! configure DMA
dma_cfg();
//! fall in sleep and NEVER wake up.
PM_SLEEP(DEEP_SLEEP);
}
先顶一下。。。。 DMA_CHN_CFG(
......
DMA_TSF_CFG(
......
),
);
那个拖尾的逗号是必须的吗?
如果不是,我觉得应该去掉。
“如果在其他由逗号分隔的列表(如枚举声明、单行多变量声明等)中也允许使用,那还说得过去,可惜事实并非如此。”
catwill 发表于 2013-11-7 13:51 static/image/common/back.gif
DMA_CHN_CFG(
......
DMA_TSF_CFG(
这不是多余的……因为在链表模式下……允许有多个Transfer,也就是多个DMA_TSF_CFG,不过删除也可以的 更新了链式传输——使用单通道完成四次不同类型的传输,传输之间既有自动切换,也有手动触发切换
这里,需要手动触发的地方,可以是硬件触发,也可以是例子中的软件触发。 小白求指点:
1:看不懂下面的含义 Memory repeat 函数,后两个参数是神马意思?
MREPEAT(TEST_COUNT, SOURCE_DATA, 0)
2:DMA_CHN_CFG 里面为什么包含DMA_TSF_CFG函数?分成两个不好?
3:UBOUND(chSource),是神马意思
4:DMA_IS_CHN_TSF_CPL名字有待考究 CPL complete
5:DMA_MANUAL_TRIGGER_NEXT, 建议去掉NEXT,多余
6:最后一个链表的例子有意思,想知道DMA_CHN_CFG的实现方式,可变宏?
7:CHANNEL_BLOCK0_ADDR, 啥意思? 版主你好 yg? 还是不习惯太多的大写{:lol:} 不明觉厉 顶个帖。 MREPEAT(TEST_COUNT, SOURCE_DATA, 0)是啥东东,函数么?怎么后面又没有分号呢?看着很蛋疼 {:cry:}说好的展开风格呢? 帮顶…… STM32_Study 发表于 2013-11-8 12:04 static/image/common/back.gif
说好的展开风格呢?
DMA太复杂了,展开会死人的……真的……相信我…… sbk100 发表于 2013-11-8 11:49 static/image/common/back.gif
MREPEAT(TEST_COUNT, SOURCE_DATA, 0)是啥东东,函数么?怎么后面又没有分号呢?看着很蛋疼 ...
这个其实无关紧要……只是用来做数据填充的,真不是这个例子的一部分…… 内部的宏不知道什么意思。
依楼主上一个模版来看,上面的几个例子,应该是主程序把。
说实话我还是比较喜欢一条一条的配置外设,这样封装的太厉害,有点摸不着北。 meirenai 发表于 2013-11-8 23:33 static/image/common/back.gif
内部的宏不知道什么意思。
依楼主上一个模版来看,上面的几个例子,应该是主程序把。
说实话我还是比较喜欢 ...
作为一种讨论,你是不是平时不太喜欢使用黑盒子呢? 学习一下{:biggrin:}{:smile:} 忍不住吐个槽
现在的经济环境真的已经差到这种程度了么?还是说现在的人心已经浮躁到没人愿意搞技术的程度了?
傻孩子才刚发出来的技术帖,竟然3天没有人回复、竟然6天里只有不到20个回复,不可思议。 eduhf_123 发表于 2013-11-12 21:26 static/image/common/back.gif
忍不住吐个槽
现在的经济环境真的已经差到这种程度了么?还是说现在的人心已经浮躁到没人愿意搞技术的程度 ...
只能说用DMA的环境和场合不多,大家也就凑合着搬运版运数据而已。过几天我放出来中断系统的接口,
一定砖头多的能盖房子了。 这个结构体定义的有点太偏底层了,可能和DMA的设计是一一对应的(看到你的定义,我就想起了DMA的实现);
建议封装的靠上层一点(个人感觉,就是软件使用者不太需要了解太多的底层);
另外,增加异步回调机制是不是会好点;这样在发出DMA后,不需要那个WHILE了;
可以简化单次和链表,统一一种模式(越简单越好),功能都是可以实现的;
本帖最后由 Gorgon_Meducer 于 2013-11-13 12:13 编辑
jm2011 发表于 2013-11-13 11:20 static/image/common/back.gif
这个结构体定义的有点太偏底层了,可能和DMA的设计是一一对应的(看到你的定义,我就想起了DMA的实现);
建 ...
其实这个已经比较高层了,更底层的部分已经屏蔽了。回调机制其实是有的,是基于硬件中断的,
由于中断系统的架构还在改进,所以暂时只能使用查询来展示接口。
另外,你仔细看,单次和链表实际上是一样的啊,DMA_TSF_CFG() 块只有一个,就是单次的,
有多个就是链表。
关于你说的希望更高层一点,能否给出一点例子呢? 恩,个人考虑,可以给当前的通道单独提炼一个cfg的函数,来设置一些公共的属性;
像参数DMA_MANUAL_TRIGGER_NEXT, DMA_MODE_PERIPHRAL_TO_MEMORY等用户不用知道这些,这些在内部自己处理;
参数DMA_DATA_BITS_32也不用每次都设置,直接设置当前channel一次就可以;
另外就是关于LIST的最好再细化一下,可以细化为:Element 和 List结构;
如果是当前DMA就一次那么就只有一个Element,多个块就有多个Element;
至于上面的一些DMA的参数,放在另外的结构体里保存;
这样比较符合程序员的思维,一人之言,如有不周,敬请谅解:)
本帖最后由 Gorgon_Meducer 于 2013-11-13 17:39 编辑
jm2011 发表于 2013-11-13 13:32 static/image/common/back.gif
恩,个人考虑,可以给当前的通道单独提炼一个cfg的函数,来设置一些公共的属性;
像参数DMA_MANUAL_TRIGGER ...
恩,个人考虑,可以给当前的通道单独提炼一个cfg的函数,来设置一些公共的属性;
答:如果单独提炼一个CFG函数,会破坏一致性,而且实际上,对一个通道来说,其实没有公共属性。
像参数DMA_MANUAL_TRIGGER_NEXT, DMA_MODE_PERIPHRAL_TO_MEMORY等用户不用知道这些,这些在内部自己处理;
DMA_MANUAL_TRIGGER_NEXT, DMA_MODE_PERIPHRAL_TO_MEMORY 这些是用户必须指定的信息,对驱动来说,这些信息
是无法确定的,必须要用户指定,其实类似 DMA_MODE_PERIPHRAL_TO_MEMORY 这样的宏,已经是进行封装以后的结果了,
它规定了数据传输时候源地址和目标地址的指针处理方式(是否自动加,加多少之类的),对于这种已经可以确定的逻辑,我们
是可以封装成模板的(DMA_MODE_PERIPHRAL_TO_MEMORY就是模板),但是对于只有用户知道的信息,而驱动无法确定的
信息,我们是无法进行信息封装的。
参数DMA_DATA_BITS_32也不用每次都设置,直接设置当前channel一次就可以;
DMA_DATA_BITS_32 不是公共信息,因为每次传输的数据类型都不同,如果非要实现你说的这种功能,恐怕只有使用C++这种
天然支持类的语言,通过基类的方式来实现了。你说的很有道理,但目前用的是C语言,要实现这个功能,同事又不增加太多代码
的复杂度,难度有点大,不过既然需求提出来了,我会认真考虑实现方式的。
另外就是关于LIST的最好再细化一下,可以细化为:Element 和 List结构;
如果是当前DMA就一次那么就只有一个Element,多个块就有多个Element;
至于上面的一些DMA的参数,放在另外的结构体里保存;
这个是可以修改的,目前实际上DMA_TSF_CFG就是一个Element,但即便这么做了未必讨好,因为很多嵌入式开发人员并不熟悉
这种链表结构,对他们来说Transfer也许更直接一点,这一点我再仔细考虑下。
这样比较符合程序员的思维,一人之言,如有不周,敬请谅解:)
非常感谢你的反馈,我希望针对我的回复,我们能有更多的讨论。请看看新的Chain List风格如何?
我们现在尝试减少参数的数量,并把每一次传输触发下一次传输的方式的设定改为默认是自动的,可以通过特殊指定的方式将其
改为手动。过一会儿将更新新的接口。
我觉得一个东西最难的地方,就是用自己的表达方式来完全描述它,最起码,这点,我要向楼主学习,DMA既然高级,难学,但更应该去掌握,顶楼主! Robin_King 发表于 2013-11-6 18:10 static/image/common/back.gif
先顶一下。。。。
这位的头像要不要这么销魂啊 mark{:smile:} 正在mark中。。。。。 楼主能否给点完整的代码,我等入门的人可以先在系统上跑起来 wazhiyi 发表于 2013-12-18 10:24
楼主能否给点完整的代码,我等入门的人可以先在系统上跑起来
这目前只是风格调查,还没有办法针对其它家芯片做出驱动。以后我们的开源团队会尝试补对其他它家的芯片支持。
另外,对目前的DMA接口风格,有什么看法么?随便说说都没关系。 Gorgon_Meducer 发表于 2013-12-18 10:32
这目前只是风格调查,还没有办法针对其它家芯片做出驱动。以后我们的开源团队会尝试补对其他它家的芯片支 ...
我想现在我的STM32系统上跑起来在看看有没有不妥的地方,光看代码,好抽象,可能太菜了 本帖最后由 Gorgon_Meducer 于 2013-12-18 16:44 编辑
wazhiyi 发表于 2013-12-18 10:51
我想现在我的STM32系统上跑起来在看看有没有不妥的地方,光看代码,好抽象,可能太菜了 ...
目前还不支持STM32,主要看DMA的特性,不一定能封装出相同的功能,可能更强,也可能更弱。这里只是从
接口的角度来征求风格以及使用便利性上的意见。 在二楼更新使用DMA配合信号网络系统通过梦游模式实现传感器数据读取的例子。 个人喜欢把左括号另起一行,呵呵,各人风格不一样的啦 自己太菜了,有点看不懂啊!
页:
[1]