DSP

DSP 2812: 使用C++实现的SCI主动站程序框架

2019-07-13 17:50发布

控制器与外界通讯,一般都会使用一些约定的通讯协议,或者使用类似于linux控制台的字符终端交互。 大体上有两种通讯模式。一种是接收上位机之类的主机的命令,并执行和输出命令的执行结果或状态。在这种被动模式,开发板称为从动站;另外一种是定时发送命令给其他控制板或者仪表,读取或者控制其他设备。在这种主动模式,开发板被称为主动站。
我们创建了这两张模式下的程序框架。 这里介绍的是做为主动站的程序框架。

应用程序的编写

首先展示一下应用程序中是如何使用这个驱动的:

第一步初始化串口设备

scia().setBps(9600,board.clock());
我们这里使用的SCIA,设置通讯速率是9600.其他参数使用默认值。

第二步定义Scia主站类对象,并设置收发缓冲区:

unsigned char sciRxBuf[256]; unsigned char sciTxBuf[256]; NDm::NApp::NF281x::CSciAMaster& sciaMaster = NDm::NApp::NF281x::CSciAMaster::ins(); sciaMaster.setRxBuf(sciRxBuf,256); sciaMaster.setTxBuf(sciTxBuf,256); sciaMaster.reset();

第三步在应用程序中实现报文处理的业务程序。

基本就是这样的循环:发送数据,然后接收数据,处理数据。
我的例子程序使用调试助手,接收DSP发来的start后,就发数据,然后DSP回复发送的数据,再发数据,再回复这样的循环。 unsigned char buf[10] = "start "; int num = 6; sciaMaster.send(buf,num); while( true ){ num = sciaMaster.recv(buf,10); if( num>0 ){ sciaMaster.send(buf,num); } }

驱动程序的设计

驱动程序时设计一个基类,然后为各串口实现子类。在子类中设定中断向量和相应的处理串口类。

基类的设计

namespace NF281x { /** * SCI主模块驱动 * 由作为主动站的程序调用本类接口。 */ class CSciMaster { public: typedef TRingBuf CRingBuf; typedef CRingBuf::CRingPos CRingPos; public: CSciMaster( NDm::NHw::NF281x::CSci& sci );
private: CRingBuf m_rx; CRingPos m_rxPos; CRingBuf m_tx; CRingPos m_txPos; NDm::NHw::NF281x::CSci& m_sci; NDm::NApp::NF281x::CPieCtl& m_pie; }; } /* namespace NF281x */
这个类使用了两个循环缓冲区,用于缓存接收到的数据和要发送的数据。 定义了简单使用的接口函数 void reset(); bool isError()const; inline NDm::NHw::NF281x::CSci& sci(){ return m_sci; } inline const NDm::NHw::NF281x::CSci& sci()const{ return m_sci; } inline void setTxBuf( unsigned char* buf,const unsigned int& size ){ m_tx.setBuf(buf,size); } inline void setRxBuf( unsigned char* buf,const unsigned int& size ){ m_rx.setBuf(buf,size); } inline bool ifTxEmpty()const{ return m_tx.getPos()==m_txPos; } inline bool ifRxEmpty()const{ return m_rx.getPos()==m_rxPos; } int send( const unsigned char* buf,const int& size ); int recv( unsigned char* buf,const int& size );
应用程序主要使用的函数就是send和recv函数。 因为驱动程序自带缓冲区,所以应用程序调用收发函数后,可以立即返回去处理其他业务,而不用等到串口数据发送完成。 /** * 中断处理函数 * 这些函数应该在中断函数中被调用执行 * 用户程序应该执行PIE级别的ACK操作 */ void irsRx(); void irsTx(); inline NDm::NHw::NF281x::CPie& pie(){ return m_pie; }
真正的在SCI设备上收发数据在irsRx()和irsTx()中实现。这两个函数由中断调用。 这块可以详细看看收发函数是如何实现的。 void CSciMaster::irsRx(){ unsigned char buf[16]; unsigned int size; if( m_sci.isInt_rxFf() ){ // 接收中断,将接收到的数据保存到缓存中 size = m_sci.rx(buf,16); if( size>0 ) m_rx.push(buf,size); m_sci.clrInt_rxFf(); }else{ reset(); } }
可以看出来,接收中断中,只是将RXFIFO中的数据转移到接收的循环缓冲区中,并清除接收中断标志。 而对于发送的过程可能要复杂一些。 因为发送数据是由中断之外触发的。 int CSciMaster::send( const unsigned char* buf,const int& size ){ int rt = m_tx.getSize() - m_tx.getLen(m_txPos); if( rt>size ) rt = size; if( rt<=0 ) return 0; m_tx.push(buf,rt); m_sci.disInt_txFf(); // 禁用发送中断 startTx(); return rt; }
发送函数将要发送的数据存入发送缓冲区中。在这个过程中,如果有发送中断到来,是不会影响send函数的正确性的,因为发送缓冲区,使用了两个pos对象,一个是写,一个是读。 但是真要发送数据时,是必须要禁止发送中断的。然后将缓冲区中的数据写到TXFIFO中,这个过程由startTx()函数实现。 为什么要有startTx()函数,因为这个过程在发送中断中也使用了。 void CSciMaster::irsTx(){ startTx(); // 如果队列为空,则停止中断 if( ifTxEmpty() ) m_sci.disInt_txFf(); }
startTx()函数做了什么? void CSciMaster::startTx(){ unsigned char buf[16]; unsigned int size; size = m_tx.getData(buf,16,m_txPos); if( size>0 ){ size = m_sci.tx(buf,size); m_txPos += size; } // 清除中断标志 m_sci.clrInt_txFf(); // 如果队列为空,则停止中断 if( ifTxEmpty() ) m_sci.disInt_txFf(); else m_sci.enInt_txFf(); }
这样的话,我将recv()函数的实现也展示出来: int CSciMaster::recv( unsigned char* buf,const int& size ){ int rt = m_rx.getLen(m_rxPos); if( rt>size ) rt = size; if( rt<=0 ) return 0; m_rx.getDataAndMovePos(buf,rt,m_rxPos); return rt; } 这样的话,这个驱动就算是完整了。 其实这样的程序要考虑清楚各种并非事件之间的关系,成员变量是否会受影响,是否会影响程序的正确性,也不是一件容易的事情。 还好,这块的代码经过测试和使用,是没问题的。

其实Ti的实时操作系统SYSBIOS中的DEV模型也是非常好的框架,我在28335平台上就是实现了一个DEV的驱动。

子类的实现

以Scia为例 namespace NF281x { class CSciAMaster:public CSciMaster{ CSciAMaster(); public: static CSciAMaster& ins(); };
在其构造函数中需要对其进行特殊化处理 CSciAMaster::CSciAMaster():CSciMaster(NDm::NApp::NF281x::CSciaCtl::ins()){ CCpu::dint(); pie().setIrs_rxAInt(irsDm281xSciAMasterRx); pie().setIrs_txAInt(irsDm281xSciAMasterTx); CCpu::eint(); CCpu::ertm(); } CSciAMaster& CSciAMaster::ins(){ static CSciAMaster i; return i; }
可以看出,还需要两个中断函数进行封装 extern "C" interrupt void irsDm281xSciAMasterRx(){ CSciAMaster& sci = CSciAMaster::ins(); sci.irsRx(); sci.pie().ack_rxAInt(); } extern "C" interrupt void irsDm281xSciAMasterTx(){ CSciAMaster& sci = CSciAMaster::ins(); sci.irsTx(); sci.pie().ack_txAInt(); }
中断函数的处理也比较简单,只需要将执行权传递给相应的中断处理成员函数即可。