当前位置: 首页 > news >正文

TMS320C55xx——AIC23B的进阶使用_中断与DMA+中断

芯片:TMS320C5509A

代码工程:项目首页 - TMS320C5509A - GitCode

代码文件BSP/aic23.h Drivers/zq_dma.h Drivers/zq_bsp.h Drivers/zq_i2c.h

前提条件:TMS320VC5509的上手教程__CCS和CLion开发TI工程(间断更新中)

引言

        在嵌入式系统中,高效的数据传输是提升系统性能的关键。TMS320C5509A作为一款高性能数字信号处理器,其内置的DMA控制器能够在CPU不干预的情况下完成外设与存储器之间的数据传输。本文将介绍AIC23B的进阶使用,从中断到DMA逐级讲解,因此AIC23B的初始化不是本篇介绍的重点。

        AIC23B在TMS320C5509A中常被用作ADC或DAC,其通过I2C配置AIC23B的寄存器参数,通过McBSP(通常使用DSP模式,另一种模式是I2S)进行音频数据通信。

        详情见AIC23B_DMA分支,DMA使用代码在Core/core.h文件里

一、轮询

 1,I2C初始化

        I2C初始化的重点是时钟配置正确即可

  • PSC        I2C的预分频寄存器,可以通过其把CPU系统时钟分频成8~12MHz,以便于配置后续时序
  • CLKL、CLKH        I2C时序中的高低电平周期,与前面的PSC共同发挥作用确定I2C的通信频率

        晶振时钟为12MHz,系统时钟为192MHz(倍频系数为16),为获得12MHz,需要把I2C的PSC寄存器设置为(16-1)。为达到400KHz的通信速率,SCL的高低电平需要设置为12MHz/400KHz=30个周期,由于SCL的高低电平分别自带6和4个周期,因此,CLK_L和CLK_H都设置为(30-10)/2=10个周期即可

        static void init(const i2c::Config &cfg){using namespace i2c::detail;//置I2C控制器复位MD::IRS::clear();// 模块时钟 = 系统时钟 / (PSC + 1)// SCL周期 = (CLKL + CLKH) / 模块时钟// 目标:SCL频率 = 400KHz// 步骤1:计算预分频器PSC(生成约8-12MHz模块时钟)const uint32_t module_clock = 12000000; // 目标模块时钟12MHzconst uint16_t psc = cfg.system_clock / module_clock - 1;PSC::write(psc);// 步骤2:计算CLKL/CLKH(400KHz SCL)const uint16_t total_cycles = module_clock / cfg.bitrate-10; // 30 cycles @12MHzconst uint16_t clk_l = total_cycles / 2;   // 低电平周期 = 10(+6)const uint16_t clk_h = total_cycles - clk_l; // 高电平周期 = 10(+4)CLKL::write(clk_l);CLKH::write(clk_h);//设置主和从地址OAR::write(0x7F);//将I2C控制器从复位中取出,置于主模式MD::or_mask(MD::IRS::MASK|MD::MST::MASK);}

        此外,I2C的发送和读取也很重要,此处以发送为例。在AIC23B中,I2C常用来配置寄存器参数的,不涉及大量数据传输,因此可以定义如下函数专门配置寄存器参数。

        static void send(const uint16_t device, const uint16_t reg_addr, const uint16_t data){using namespace i2c::detail;// 可以使用寄存器位域里的MASK,比如MD::STP::MASK// 1. 配置数据计数 (2字节: 寄存器地址 + 数据)CNT::write(2);// 2. 设置从机地址 (7位模式)SA::write(device & 0x7F);// 3. 配置模式寄存器:MD::or_mask(MD::STT::MASK | //   - 产生START条件 (STT)MD::STP::MASK | //   - 产生STOP条件 (STP)MD::MST::MASK | //   - 主机模式 (MST)MD::TRX::MASK   //   - 发送模式 (TRX)// MD::IRS::MASK   //   - 启用模块 (IRS));// 4. 发送寄存器地址 (等待发送就绪)DX::write(reg_addr & 0xFF);while (STR::XRDY::read_bit_not()) {}// 等待 XRDY (ICSTR_ICXRDY)// 5. 发送数据 (等待发送就绪)DX::write(data & 0xFF);while (STR::XRDY::read_bit_not()) {}// 等待 XRDY}

2,McBSP初始化

        确保McBSP配置为如下模式:

  • 帧参数                  16位(或者32位),单相,无延迟(有延迟会导致波形输出有问题)
  • 帧同步                   最小化极性配置,输入0即可,否则波形输出畸形
  • 采样率发生器        启用采样率发生器,确保不会产生严重的时钟漂移
            static void init(const bool receive_IT =false, const bool transmit_IT=false){// McBSP0复位regs::spcr1::clear();regs::spcr2::clear();// 配置帧参数(16位,单相,无延迟)regs::xcr1::write(info::XCR1::XWDLEN1_16);regs::xcr2::write(info::XCR2::XPHASE_SINGLE | info::XCR2::XDATDLY_0);regs::rcr1::write(info::RCR1::RWDLEN1_16);regs::rcr2::write(info::RCR2::RPHASE_SINGLE | info::RCR2::RDATDLY_0);/// 使用帧同步会出问题// 禁止内部生成帧同步信号,配置为从模式使用FSX引脚的外部帧同步// 帧同步高电平有效,数据在CLKX时钟上升沿进行采样// regs::pcr::write(info::PCR::CLKXP);// 最小化极性配置regs::pcr::write(0); // 所有极性默认(上升沿有效,高电平有效)// 关键:启动采样率发生器!!!regs::spcr2::GRST::set();  // 启动采样率发生器systick::Delay::us(10); // 等待稳定// 设置接收中断模式为 "RRDY 触发中断" (RINTM = 00)regs::spcr1::RINTM::write_bits(0);//发送器摆脱复位  接收器使能regs::spcr1::or_mask(info::SPCR1_MASK::RRST);regs::spcr2::or_mask(info::SPCR2_MASK::XRST);if (receive_IT)start_receiveIT();if (transmit_IT)start_transmitIT();}

3,AIC23B初始化

         AIC23B初始化是建立在I2C初始化和McBSP初始化基础上的。在本文中,AIC23B可作为ADC和DAC使用,为保证作为ADC时读取数据噪声更少、效果更好,因此选用线输入,并且只读取单个通道(麦克风输入只有一个通道,两个寄存器里的值是相同的)

            /**** @param receive_IT true表示立即开启接收中断* @param transmit_IT true表示立即开启发送中断*/template<SAMPLE_RATE::Type sample_rate>static void init(const bool receive_IT =false, const bool transmit_IT=false){// 初始化I2Czq::i2c::Config cfg;cfg.system_clock = 192000000;cfg.bitrate = 400000;cfg.loopback = false;zq::I2C::init(cfg);// 初始化AIC23寄存器write_cmd(detail::REG::RESET, 0); //复位寄存器write_cmd(detail::REG::POWER_DOWN_CTL, 0); //所有电源都打开// write_cmd(detail::REG::ANALOG_AUDIO_CTL, detail::ANAPCTL::DAC | detail::ANAPCTL::INSEL); //打开DAC 选择传声器 选择麦克风输入(MICIN)write_cmd(detail::REG::ANALOG_AUDIO_CTL, detail::ANAPCTL::DAC); //打开DAC 选择传声器 选择线输入write_cmd(detail::REG::DIGITAL_AUDIO_CTL, 0); //数字音频通道控制   禁止去加重// 打开线入声道音量write_cmd(detail::REG::LT_LINE_CTL, 0x01F); //左声道输入衰减正常,最小为-34.5dBwrite_cmd(detail::REG::RT_LINE_CTL, 0x01F); //右声道输入衰减正常,最小为-34.5dB  禁止左右声道同时更新// 数字音频接口主模式    输入长度16位     DSP初始化// 采样率控制 44.1KHz 比较常用   SRC_BOSR为272fs  USB clockwrite_cmd(detail::REG::DIGITAL_IF_FORMAT,detail::DIGIF_FMT::MS | detail::DIGIF_FMT::IWL_16 | detail::DIGIF_FMT::FOR_DSP);// 理论上0x01是48KHz,0x1D是96KHz,0x23是44.1KHz// write_cmd(detail::REG::SAMPLE_RATE_CTL, 0x1D);set_sample_rate(sample_rate);// 打开耳机声道音量和数字接口激活write_cmd(detail::REG::LT_HP_CTL, 0x1ff); //激活 衰减+6dB 零点检测  开启write_cmd(detail::REG::RT_HP_CTL, 0x1ff);write_cmd(detail::REG::DIG_IF_ACTIVATE, detail::DIGIFACT::ACT);// 初始化McBSPzq::mcbsp::Control::init(receive_IT,transmit_IT);}

4,轮询读取或发送

        AIC23B的读取和发送数据是依靠McBSP完成的,所谓轮询,即不断询问标志是否完成,再决定是否读取或发送。显而易见,CPU的大部分时间都会浪费在判断标志是否完成的过程

        STATIC_ASSERT此处是已经定义好的宏,见Drivers/zq_conf.h文件
            /*** 读取通道数据* @tparam T 数据类型,可为short或者volatile short* @param data_1 通道1数据* @param data_2 通道2数据* @note 此函数在调试时小心,调试时有可能导致调试窗口无法以图像的形式显示,重启即可。不能显示的表面原因是:Current CPU is null*/template<typename T>static void read(T& data_1,T& data_2){STATIC_ASSERT(sizeof(T)==sizeof(short),"data type is not short!");// 等待McBSP0准备好while (regs::spcr1::RRDY::read_bit_not()){}data_1 = regs::drr1::read();data_2 = regs::drr2::read();}static void write(const uint16_t data_L, const uint16_t data_R){// 等待McBSP0准备好while (regs::spcr2::XRDY::read_bit_not()) {}//  此处应该使用XRDYregs::dxr1::write(data_L);regs::dxr2::write(data_R);}

        常见用法是在main函数的while循环里调用读取或者发送函数,弊病也显而易见,如果在while循环里有其他耗时任务,则会导致读取或者发送会错过指定时间(AIC23B读取或者发送数据,都由AIC23B的采样率寄存器控制)。

int main()
{// 初始化……short data_1,data_2;while(1){// 轮询方式读取数据zq::mcbsp::Control::read(data_1,data_2);// 其他任务……}}

        聪明一点的做法是使用定时器中断读取或者发送数据,但仍是轮询的方式,大部分时间仍会浪费在轮询标志过程。如果定时器中断频率设置太低,会导致读取发送数据不及时,设置太高,甚至会因为轮询的原因导致程序一直处在定时器中断里,无法进入主程序。

void interrupt Timer0_ISR()
{zq::mcbsp::Control::read(data_1,data_2);}

        好一点的办法是把read或write函数里的while轮询标志改成if判断(需注意判断条件要反过来),不过需要定时器频率高于采样率频率,不然会遗漏数据

            static void write_inTimerISR(const uint16_t data_L, const uint16_t data_R){// 等待McBSP0准备好if (regs::spcr2::XRDY::read_bit()) {regs::dxr1::write(data_L);regs::dxr2::write(data_R);}//  此处应该使用XRDY}

 二、中断

1,简介

        中断正是为了解决频繁轮询标志导致CPU计算资源被严重浪费的问题,在TMS320C55xx的可屏蔽中断中,中断使能分为CPU中断和外设中断两部分,前者使能只能确保CPU接收到中断信号后去执行,后者只能确保外设能发出中断信号。因此想要开启某个外设的中断,需要把CPU中断和外设中断都要使能,下面将以McBSP0为例。

        在使能中断前,需要注册好中断向量,中断向量是在中断向量表汇编文件定义的,McBSP0有接收和发送两个中断向量,先使用.ref声明两个中断函数(函数名前需要加上下划线_,并且.ref不能顶格写)

        在中断向量表中,RINT0和XINT0分别表示McBSP0的读取和发送中断向量

        注册好之后,需要把声明的函数实现,需确保函数定义是以C符号链接,如果是C++环境,需要把函数定义囊括在extern "C"内

void interrupt McBSP0_Receive_ISR()
{// ……
}void interrupt McBSP0_Transmit_ISR()
{// ……
}

        在使能McBSP0的中断后,可以把发送、读取相关任务放在中断里了,不过此时需要把轮询标志的过程去除(if判断标志也可以不加,因为McBSP接收中断一般用于RRDY中断使能,后面会说)

            // 读取第一个通道的数据template<typename T>static void read_data1_IT(T &data_1){STATIC_ASSERT(sizeof(T)==sizeof(short),"data type is not short!");// 等待McBSP0准备好if (regs::spcr1::RRDY::read_bit())data_1 = regs::drr1::read();}static void write_IT(const uint16_t data_L, const uint16_t data_R){if (regs::spcr2::XRDY::read_bit()){regs::dxr1::write(data_L);regs::dxr2::write(data_R);}}

        前面介绍了中断向量的设置,接下来将介绍如何使能MCBSP0的中断

 2,使能中断

        依前面所言,中断使能分成CPU和外设两部分,首先是外设中断使能,在前面的McBSP初始化函数里可以看到下面这行代码,McBSP的SPCR1寄存器里有个RINTM的字段,用于设置McBSP的接收中断使能模式,一般配置为0,即RRDY触发中断模式,意为接收数据就绪时(RRDY置1),触发中断。此外还有帧结束触发中断模式。

        发送中断同理,在SPCR2寄存器中,默认为0,即XRDY触发中断模式。

        然后是CPU中断使能,在IER0和IER1两个中断使能寄存器中找到对应的字段,RINT0在IER0中,XINT0在IER1中。此外,还需要把调试中断寄存器DBIER0和DBIER1配置成IER0和IER1一样,确保调试时中断能正常使用

                cpu::IER0::RINT0::set();cpu::DBIER0::RINT0::set();

        如果你的工程是C工程,那么可以自行查手册获取寄存器的字段配置,或者在Drivers/zq_cpu.h中找到你需要的寄存器字段声明,如下RIN0IER0的第5位,IER0地址为0x0000

3,启用中断读取或发送数据

         这一点尤为重要,在前面初始化中断的情况下,想要正确读取或发送数据还需要其他配置。因为中断使能后,并不会直接自动进行数据的收发,还需要发送特定的事件

            static void start_receiveIT(){// 启动接收中断cpu::IER0::RINT0::set();cpu::DBIER0::RINT0::set();volatile uint16_t temp;read_data1(temp);// 必须先读取一次数据(重点在读drr寄存器),之后的中断才能正常触发}static void start_transmitIT(){// 启动接收中断cpu::IER1::XINT0::set();cpu::DBIER1::XINT0::set();write(0xFFFF, 0xFFFF);}

        如上,可以看到启动发送或者接收中断的函数里,处理使能中断外,还在后面加上了读取或者发送数据的过程。正如前面所说,需要主动产生发送或者读取数据的事件,才能正常触发一次中断(换成“触发下次中断”或许更好理解)。

        同理,如果想要源源不断读取或者发送数据,那么需要在接收或者发送中断里,调用发送或者读取数据的函数,即前面定义的那两个函数

void interrupt McBSP0_Receive_ISR()
{if (index_adc >= 128)index_adc = 0;zq::mcbsp::Control::read_data1_IT(buf_adc[index_adc]);++index_adc;
}void interrupt McBSP0_Transmit_ISR()
{if (index_dac >= 128)index_dac = 0;zq::mcbsp::Control::write_IT(buf_dac[index_dac], buf_dac[index_dac]);++index_dac;
}

        以读取数据为例,效果如下(对mcbsp的读取和发送函数进行了一层封装)

三、DMA+中断

1,引言

        DMA+中断方式事实上还是DMA,只不过DMA身为外设,也有自己的中断罢了。DMA中断的配置和使用相当麻烦,当然,我指的是初次对照着寄存器配置,坑实在太多。废话不过说,我们直接开始

2, 基本介绍

①架构 

       手册上C5509A有6个DMA,如DMA0等,实际上只有DMA控制器,DMA0等不过是其6个通道。其DMA控制器有以下关键特性:

  • 6个独立通道:支持6路并行数据传输

  • 4种标准端口:DARAM、SARAM、外部存储器和外设

  • 自动初始化:支持循环传输模式

  • 事件同步:可绑定到外设事件(如McBSP接收完成)

  • 双缓冲机制:配置寄存器与工作寄存器分离

②数据传输层级

        DMA传输数据是分成以下层级的

  • 字节:顾名思义,数据的基本单位

  • 元素:1-4字节的数据单元,是DMA传输的基本单位

  • :1-65535个元素组成,或者可以理解为“包”。总之,理解为传输一维数组的数据即可

  • :1-65535帧组成,可理解为传输二维数组

3,寄存器

        DMA的寄存器有不少,就不一一介绍了。从手册里可以查到,DMA寄存器分为全局寄存器和通道寄存器两种,前者是6个DMA通道共享的寄存器,后者是每个DMA通道都有的寄存器

                // ============== 全局寄存器 (所有通道共用) ==============DECLARE_MMR_REGISTER(GCR, 0x0E00)  // DMA全局控制寄存器DECLARE_MMR_REGISTER(GSCR, 0x0E02) // DMA软件兼容性寄存器DECLARE_MMR_REGISTER(GTCR, 0x0E03) // DMA超时控制寄存器

        而我们需要重点关注的只有CCR寄存器和CSDP寄存器,前者是DMA通道控制寄存器决定,后者是源/目标参数寄存器。字段如下:

                    // DMA通道控制寄存器(DMACCR)BEGIN_MMR_REGISTER(CCR, 0x0C01 + dma)// [15:14] 目标地址模式 (DSTAMODE)// 00b: 常量地址 (传输期间地址不变)// 01b: 后递增 (根据数据类型自动增加地址)// 10b: 单索引 (传输后地址增加元素索引值) 每次元素传输后地址 += 元素索引// 11b: 双索引 (帧内用元素索引,帧间用帧索引)    帧内元素索引/帧间帧索引DECLARE_MMR_BITS_FIELD(DST_AMODE, 2, 14)// [13:12] 源地址模式 (SRCAMODE)// 00b: 常量地址 (传输期间地址不变)// 01b: 后递增 (根据数据类型自动增加地址)// 10b: 单索引 (传输后地址增加元素索引值)// 11b: 双索引 (帧内用元素索引,帧间用帧索引)DECLARE_MMR_BITS_FIELD(SRC_AMODE, 2, 12)// [11] 结束编程标志 (ENDPROG)// 0: 配置寄存器可编程/编程中// 1: 编程结束 (表示CPU已完成配置)DECLARE_MMR_BIT(END_PROG, 11)// [10] 保留位 (必须写0)DECLARE_MMR_BIT(Reserved1, 10)// [9] 重复模式 (REPEAT)// 0: 仅在ENDPROG=1时重复 (需CPU握手)// 1: 无条件重复 (忽略ENDPROG状态)DECLARE_MMR_BIT(REPEAT, 9)// [8] 自动初始化 (AUTOINIT)// 0: 禁用自动初始化// 1: 使能自动初始化 (块传输完成后自动重载)DECLARE_MMR_BIT(AUTO_INIT, 8)// [7] 通道使能 (EN)// 0: 禁用通道  1: 启用通道DECLARE_MMR_BIT(EN, 7)// [6] 通道优先级 (PRIO)// 0: 低优先级  1: 高优先级DECLARE_MMR_BIT(PRIO, 6)// [5] 帧同步模式 (FS)// 0: 元素同步 (每个元素需事件触发)// 1: 帧同步 (每帧只需一个事件触发)DECLARE_MMR_BIT(FS, 5)// [4:0] 同步事件选择 (SYNC)// 00000b: 无同步事件 (立即启动)// 其他值: 选择特定外设事件作为同步源DECLARE_MMR_BITS_FIELD(SYNC, 5, 0)END_MMR_REGISTER()
                    // 源/目标参数寄存器 (DMACSDP)BEGIN_MMR_REGISTER(CSDP, 0x0C00 + dma)// [15:14] 目的突发使能 (DSTBEN)// 00b: 禁止突发  10b: 使能突发  其他: 保留DECLARE_MMR_BITS_FIELD(DSTBEN, 2, 14)// [13] 目的打包使能 (DSTPACK)// 0: 不打包  1: 打包DECLARE_MMR_BIT(DSTPACK, 13)// [12:9] 目的端口选择 (DST)// 0000b: SARAM  0001b: DARAM// 0010b: 外部内存  0011b: 外设DECLARE_MMR_BITS_FIELD(DST, 4, 9)// [8:7] 源突发使能 (SRCBEN)// 00b: 禁止突发  10b: 使能突发  其他: 保留DECLARE_MMR_BITS_FIELD(SRCBEN, 2, 7)// [6] 源打包使能 (SRCPACK)// 0: 不打包  1: 打包DECLARE_MMR_BIT(SRCPACK, 6)// [5:2] 源端口选择 (SRC)// 0000b: SARAM  0001b: DARAM// 0010b: 外部内存  0011b: 外设DECLARE_MMR_BITS_FIELD(SRC, 4, 2)// [1:0] 数据尺寸 (DATATYPE)// 00b: 8位  01b: 16位  10b: 32位  11b: 保留DECLARE_MMR_BITS_FIELD(DataSize, 2, 0)END_MMR_REGISTER()

        注意到CCR寄存器的低5位为SYNC字段,即同步事件。DMA传输数据是设置同步事件的,不然DMA不知道要传输什么,因为不是所有的外设都能使用DMA。

        同步事件定义如下:

                // DMA同步事件定义DECLARE_ATTRIBUTE(Event,None = 0x00,               // 00000b: 无同步事件McBSP0_Receive = 0x01,     // 00001b: McBSP0接收事件(REVTO)McBSP0_Transmit = 0x02,    // 00010b: McBSP0传输事件(XEVT0)McBSP1_Receive = 0x05,     // 00101b: McBSP1/MMC-SD1接收事件McBSP1_Transmit = 0x06,    // 00110b: McBSP1/MMC-SD1传输事件McBSP2_Receive = 0x09,     // 01001b: McBSP2/MMC-SD2接收事件McBSP2_Transmit = 0x0A,    // 01010b: McBSP2/MMC-SD2传输事件Timer0_Int = 0x0D,         // 01101b: Timer0中断事件Timer1_Int = 0x0E,         // 01110b: Timer1中断事件EXT_INT0 = 0x0F,           // 01111b: 外部中断0EXT_INT1 = 0x10,           // 10000b: 外部中断1EXT_INT2 = 0x11,           // 10001b: 外部中断2EXT_INT3 = 0x12,           // 10010b: 外部中断3EXT_INT4_or_I2C_Rec = 0x13,// 10011b: 外部中断4/或I2C接收事件I2C_Transmit = 0x14,       // 10100b: I2C传输事件(XEVT12C)_RSVD_00011b = 0x03,       // 00011b: 保留_RSVD_00100b = 0x04,       // 00100b: 保留_RSVD_00111b = 0x07,       // 00111b: 保留_RSVD_01000b = 0x08,       // 01000b: 保留_RSVD_01011b = 0x0B,       // 01011b: 保留_RSVD_01100b = 0x0C,       // 01100b: 保留_RSVD_Other = 0x15         // 10101b及以上: 保留)

4,初始化 

        由于DMA寄存器需要配置的参数很多,为便于配置,需要定义一个结构体来辅助初始化

            // DMA配置结构体struct Config{// --- CSDP寄存器配置 ---unsigned int dstBen; // [15:14] 目的突发使能unsigned int dstPack; // [13] 目的打包使能unsigned int dstPort; // [12:9] 目的端口选择 这个端口不能设置错误,否则DMA无法正常启动unsigned int srcBen; // [8:7] 源突发使能unsigned int srcPack; // [6] 源打包使能unsigned int srcPort; // [5:2] 源端口选择 这个端口不能设置错误,否则DMA无法正常启动unsigned int dataSize; // [1:0] 数据尺寸// --- CCR寄存器配置 ---unsigned int dstAddrMode; // [15:14] 目标地址模式unsigned int srcAddrMode; // [13:12] 源地址模式bool fs; // [5] 帧同步模式bool priority; // [6] 通道优先级bool autoinit; // [8] 自动初始化bool mode; // [9] 重复传输模式// 索引配置(二维数据传输)short srcElementIndex; // 源元素索引short srcFrameIndex; // 源帧索引short dstElementIndex; // 目标元素索引short dstFrameIndex; // 目标帧索引// 构造函数设置默认值Config() : dstBen(0), dstPack(0), dstPort(0),srcBen(0), srcPack(0), srcPort(0),dataSize(1), // 默认16位数据dstAddrMode(0), srcAddrMode(0),fs(false), priority(false), autoinit(false), mode(false),srcElementIndex(0), srcFrameIndex(0),dstElementIndex(0), dstFrameIndex(0){}};

        那么初始化函数实际上就是对CCR寄存器和CSDP寄存器进行初始化(注释都很详细,不赘述了)。在前面的中断介绍里提到,中断使能分为两部分,这里可以提前使能CPU的DMA中断,后面只需要启闭DMA的中断使能寄存器即可控制DMA的中断使能。

            // 使用自定义配置初始化static void init(const Config &config){// 禁用通道CCR_REG::EN::clear();// 配置CSDP寄存器CSDP_REG::DSTBEN::write_bits(config.dstBen);CSDP_REG::DSTPACK::write_bit(config.dstPack);CSDP_REG::DST::write_bits(config.dstPort);CSDP_REG::SRCBEN::write_bits(config.srcBen);CSDP_REG::SRCPACK::write_bit(config.srcPack);CSDP_REG::SRC::write_bits(config.srcPort);CSDP_REG::DataSize::write_bits(config.dataSize);// 配置索引寄存器CEI_REG::write(config.srcElementIndex);CFI_REG::write(config.srcFrameIndex);CDEI_REG::write(config.dstElementIndex);CDFI_REG::write(config.dstFrameIndex);// 配置CCR寄存器CCR_REG::DST_AMODE::write_bits(config.dstAddrMode);CCR_REG::SRC_AMODE::write_bits(config.srcAddrMode);CCR_REG::FS::write_bit(config.fs);  // 默认关闭帧同步,即使用元素同步,确保每个元素都按照时序传输CCR_REG::PRIO::write_bit(config.priority);CCR_REG::AUTO_INIT::write_bit(config.autoinit);CCR_REG::REPEAT::write_bit(config.mode);// 清除停止标志CCR_REG::END_PROG::clear();// 默认启用CPU中断(DMA的中断没开,所以实际上中断并不会触发)cpu::IER1::DMAC0::set();cpu::DBIER1::DMAC0::set();}

        如前面介绍的中断内容一样,以DMA0为例,也需要到中断向量表里注册中断向量,然后实现中断向量函数……

 5,启用DMA传输

        DMA传输需要设置传输的事件、源/目标地址和帧大小(帧数量先不考虑),尤为需要注意的是地址。TMS320C55xx的CPU对数据进行操作的是字地址,而DMA传输需要字节地址。简单来说就是,TMS320C55xx是16位机,字长是16位,它把一个字节当成了16位,而非我们传统意义上的8位,因此需要把地址左移1位(相当于乘以2),把字地址变成字节地址。

            // 启动DMA传输(多帧)static void start(detail::info::Event::Type event, // 同步事件uint32_t srcAddr, // 源地址(32位)uint32_t dstAddr, // 目标地址(32位) I/O地址空间和存储器地址必须都要左移一位uint16_t elementCount, // 每帧元素数uint16_t frameCount = 1// 帧数量){// 禁用通道CCR_REG::EN::clear();// 配置地址寄存器srcAddr <<= 1; // I/O地址空间和存储器地址必须都要左移一位dstAddr <<= 1; // I/O地址空间和存储器地址必须都要左移一位CSSA_L_REG::write(srcAddr & 0xFFFF);CSSA_U_REG::write((srcAddr >> 16) & 0xFFFF);CDSA_L_REG::write(dstAddr & 0xFFFF);CDSA_U_REG::write((dstAddr >> 16) & 0xFFFF);// 配置传输数量CEN_REG::write(elementCount);CFN_REG::write(frameCount);// 设置同步事件CCR_REG::SYNC::write_bits(event);// 在AutoInit为1,REPEAT为0时,只要设置END_PROG=1(比如在中断里设置),就会自动开始新一轮(只有1轮)// 确保自动初始化握手完成if (CCR_REG::AUTO_INIT::read_bit()) {CCR_REG::END_PROG::set();}// 使能通道CCR_REG::EN::set();}

6,McBSP的DMA传输

         前面介绍的是DMA的一般使用流程,先初始化,然后开始传输,需注意在DMA初始化前AIC23B就已经初始化好了。现在我们McBSP的DMA传输为实例(本文以DMA0为例):

①读取

        首先,先初始化DMA,这里我们配置DMA的目的是从AIC23B读取数据到内存里的一个数组,因此源端口配置为3,指向的是外设。由于数组等变量在cmd文件里被我放到了DARAM里,因此目标端口设置为1。注意!端口不能设置错误,否则DMA无法进行传输。

        // 配置DMA0用于McBSP0接收DMA0::Config cfg;cfg.srcAddrMode = 0;       // 源地址模式:常量地址(McBSP寄存器固定)cfg.dstAddrMode = 1;       // 目标地址模式:后递增(存入数组)cfg.dataSize = 1;          // 16位数据cfg.autoinit = true;       // 自动重新加载// 这两个端口不能设置错误,否则DMA无法正常启动cfg.srcPort = 3;           // 源端口:外设(McBSP)cfg.dstPort = 1;           // 目标端口:0:SARAM(内存) 1:DARAMDMA0::init(cfg);DMA0::enableIT(true);

        前面初始化后,还会启用DMA的中断,这是因为此行的目的是读取数据,那么读取完之后需要通知CPU,那么最好的方式是以中断来通知,因此开启是blockIE,即块传输完成后触发DMA中断。

            // 配置中断static void enableIT(bool blockIE = false, // 块传输完成中断bool lastIE = false, // 最后传输中断bool frameIE = false, // 帧传输完成中断bool halfIE = false, // 半帧传输中断bool dropIE = false, // 事件丢失中断bool timeoutIE = false // 超时中断){CICR_REG::BLOCKIE::write_bit(blockIE);CICR_REG::LASTIE::write_bit(lastIE);CICR_REG::FRAMEIE::write_bit(frameIE);CICR_REG::HALFIE::write_bit(halfIE);CICR_REG::DROPIE::write_bit(dropIE);CICR_REG::TIMEOUTIE::write_bit(timeoutIE);}

        需注意,DMA中断触发后,需要手动清理标志位,而清理标志位的方式就是读取CSR寄存器即可。it_flags是DMA类里的一个成员变量,与DMA操作无关,作用是复制CSR寄存器的标志,用于在main函数主循环里通知DMA传输完成了

            // 清除中断标志static void clearInterruptFlags(){// 读取状态寄存器会自动清除标志位const volatile uint16_t status = CSR_REG::read();it_flags = status; // 对齐标志位}

        一切就绪后就可以开始DMA传输了

        // 启动DMA传输DMA0::start(dma::detail::info::Event::McBSP0_Receive,   // 同步事件mcbsp::regs::drr1::REG,                     // McBSP接收寄存器字节地址reinterpret_cast<uint32_t>(buf_adc),        // 缓冲区字节地址FFT_SIZE                                    // 元素数量);

        由于没有配置DMA循环重复传输,那么传输完成后就会自动停止。在main函数主循环里

可以读取it_flags里保存的CSR寄存器的值,进而判断是否传输完成。传输完成后就可以进行数据处理,如FFT,然后再重新传输。

int main(){// 初始化……while(1){if (DMA0::getITFlags(DMA0::IT_Flags::blockIE)) {LED::on(led::pin::LED_3);DMA0::start(dma::detail::info::Event::McBSP0_Receive,   // 同步事件mcbsp::regs::drr1::REG,                     // McBSP接收寄存器字节地址reinterpret_cast<uint32_t>(buf_adc),        // 缓冲区字节地址FFT_SIZE                                    // 元素数量);}}
}

        前面在DMA初始化过程还记得我们配置了autoinit吗?这个东西是自动初始化,意思是当DMA传输结束后,就可以自动把寄存器里的值配置为我们DMA初始化结束的时候(初始化结束的标志,就是把END_PROG置为1)。并且DMA传输结束后会自动把END_PROG清零,当我们把END_PROG寄存器置为1即可重新进行DMA传输,不需要重新调用DMA_start函数了

// zq_dma.h中/**
* @brief 重新进行传输,当且仅当autoinit设置为1的情况下
*/
static void restart()
{CCR_REG::END_PROG::set();
}// main.cpp中
int main(){// 初始化……while(1){if (DMA0::getITFlags(DMA0::IT_Flags::blockIE)) {LED::on(led::pin::LED_3);DMA0::restart;}}
}

效果如下:

 ②发送

        发送数据同理,只要交换源/目标端口就行,如果想要循环不断发送数据,那么不需要开启中断,并且把下面两个寄存器同时设置为1,这样开启传输后就会不断重复发送数据。

CCR_REG::AUTO_INIT::set();
CCR_REG::REPEAT::set();

        尤为需要注意的是,DMA接收数据虽然不像McBSP中断那样需要主动接收数据才能触发中断事件,但是DMA发送数据要。也就是说启动DMA传输后,还需要主动发送数据以产生发送同步事件,这个DMA才能正式启动传输

        // 启动DMA传输(如果是发送事件,必须让McBSP主动发送数据才能正确触发DMA)DMA0::start(dma::detail::info::Event::McBSP0_Transmit,   // 同步事件:McBSP发送reinterpret_cast<uint32_t>(buf_dac),      // 源地址:正弦波表mcbsp::regs::dxr1::REG,                     // 目标地址:McBSP发送寄存器FFT_SIZE                           // 元素数量);aic23::Control::write_data(0xFFFF,0xFFFF);// 对McBSP发送函数的一层封装

        发送方波的时候,效果如下(并非DMA的原因,而是AIC23B输出剧烈变化的电压时会有“回弹”现象):

 7,优化接口

        为了让DMA使用起来更加方便,在Drivers/zq_dma.h里还提供了另一套接口

使用效果如下(读取数据):

        // =============== 新接口初始化 ==================DMA0::config::baseInit();DMA0::config::srcAddrMode::constant();DMA0::config::dstAddrMode::increase();DMA0::config::dataSize::_16bit();DMA0::config::autoinit::enable();DMA0::config::srcPort::peripheral();DMA0::config::dstPort::dram();DMA0::config::itConfig::block::enable();// 启动DMA传输DMA0::start(dma::detail::info::Event::McBSP0_Receive,   // 同步事件mcbsp::regs::drr1::REG,                     // McBSP接收寄存器字节地址reinterpret_cast<uint32_t>(buf_adc),        // 缓冲区字节地址FFT_SIZE                                    // 元素数量);

为进一步提供便利,把平时常用的配置封装为了预设

        // =============== 预设初始化 ==================DMA0::config::mode::peripheralToMemory();// 使用预设// 启动DMA传输DMA0::start(dma::detail::info::Event::McBSP0_Receive,   // 同步事件mcbsp::regs::drr1::REG,                     // McBSP接收寄存器字节地址reinterpret_cast<uint32_t>(buf_adc),        // 缓冲区字节地址FFT_SIZE                                    // 元素数量);

由于AIC23B在使用DMA过程中,基本不会改变,于是做了进一步的封装(如果能使用C++11或者C++17,有了自动推导和静态检查,表达或许能更加简练且强大)

        aic23::Control::receiveDMA<DMA0>::init();aic23::Control::receiveDMA<DMA0>::start(buf_adc,FFT_SIZE);

使用typedef重命名后,就可以得到更加方便且不易错的形式

// 在文件的头部重命名
typedef bsp::aic23::Control::receiveDMA<zq::DMA0> aic23_ReceiveDMA;
typedef bsp::aic23::Control::transmitDMA<zq::DMA1> aic23_TransmitDMA;// 在main函数里使用int main()
{// 其他初始化……// DMA读取aic23_ReceiveDMA::init();aic23_ReceiveDMA::start(buf_adc,FFT_SIZE);// DMA发送aic23_TransmitDMA::init<true,true>();aic23_TransmitDMA::start(buf_dac,FFT_SIZE);while(true){if (aic23_ReceiveDMA::isComplete()) {LED::on(led::pin::LED_3);aic23_ReceiveDMA::restart();}// 其他任务……}}

8,实测过程中的一个问题 

         实测过程中,前0~3个数据可能会有异常,建议把数组大小扩大4个,处理数据时再忽略前4个即可

9,分享一个bug

         一天也写不了多少bug,这几天在使用std命名空间里的东西,总是会发生十分奇怪的问题,有时候是程序运行起来莫名奇妙,有些函数感觉没有加载一样。比如这次,这样写DMA0可以正常读取AIC23B数据

        for (int i = 0; i < 128; ++i){buf_adc[i] = 0;}aic23_ReceiveDMA::init();aic23_ReceiveDMA::start(buf_adc,FFT_SIZE);for (int i = 0; i < 128; ++i){buf_dac[i] = 19537 * std::sin(2 * 3.1415926 * i / 128);}aic23_TransmitDMA::init<true,true>();aic23_TransmitDMA::start(buf_dac,FFT_SIZE);

        这样写,不行

        for (int i = 0; i < 128; ++i){buf_adc[i] = 0;buf_dac[i] = 19537 * std::sin(2 * 3.1415926 * i / 128);}aic23_ReceiveDMA::init();aic23_ReceiveDMA::start(buf_adc,FFT_SIZE);aic23_TransmitDMA::init<true,true>();aic23_TransmitDMA::start(buf_dac,FFT_SIZE);

        此外,std::memcpy等函数也是如此,发生奇奇怪怪的错误,偏偏程序也不会卡死

http://www.lqws.cn/news/132103.html

相关文章:

  • [网页五子棋][匹配模块]实现胜负判定,处理玩家掉线
  • 前端面试二之运算符与表达式
  • 十六、【前端强化篇】完善 TestCase 编辑器:支持 API 结构化定义与断言配置
  • <el-table>构建树形结构
  • nest实现前端图形校验
  • yaffs2目录搜索上下文数据结构struct yaffsfs_dirsearchcontext yaffsfs_dsc[] 详细解析
  • TDengine 在电力行业如何使用 AI ?
  • AtCoder解析大全
  • 前端面试总结
  • 厂商与经销商供应链数据协同:策略、实践与深度价值挖掘
  • SecureCRT 设置超时自动断开连接时长
  • 通光散基因组-文献精读139
  • 微服务商城-用户微服务
  • 大数据Spark(六十一):Spark基于Standalone提交任务流程
  • 多模态大语言模型arxiv论文略读(107)
  • Python 入门到进阶全指南:从语言特性到实战项目
  • Go语言学习-->项目中引用第三方库方式
  • C# Wkhtmltopdf HTML转PDF碰到的问题
  • 力扣HOT100之二分查找:74. 搜索二维矩阵
  • MySQL索引(index)
  • 渗透测试服务如何全方位评估企业安全状况并揭示潜在缺陷?
  • MYSQL(二) ---MySQL 8.4 新特性与变量变更
  • Python爬虫之数据提取
  • 敏捷项目管理:重塑价值交付的动态协作范式
  • 《AI角色扮演反诈技术解析:原理、架构与核心挑战》
  • Django核心知识点全景解析
  • React 性能监控与错误上报
  • 虚拟机CentOS 7 网络连接显示“以太网(ens33,被拔出)“、有线已拔出、CentOS7不显示网络图标
  • React与原生事件:核心差异与性能对比解析
  • 2025年大模型平台落地实践研究报告|附75页PDF文件下载