完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
CC2541 timer+ADC+DMA编程心得 Cloud 2016.1.21 注意:阅读本实例请配合使用TI的CC254x系列数据手册中对寄存器的描述 蓝牙4.0芯片CC2541内部带有△ΣADC,有效位数大概有12位的样子,网上有些ADC采样的例子,但是ADC和DMA的介绍却几乎找不到,Timer+ADC+DMA的介绍更是完全没有,现测试完成Timer+ADC+DMA的编程,方法如下: 这里我的编程目的是让ADC以1KHz的采样频率采样4个通道的数据,然后由DMA自动存到内存中,当数据量达到一定程度的时候中断通知系统处理数据。 一、定时器配置 既然要采样频率是确定的,那么就需要有设备去准确的定时触发ADC,因此需要定时器。这里使用了Timer1的“模模式”,即从0计数至T1CC0然后重新从0计数,不断自动重复。可以通过写T1CC0(包含2个8位的寄存器,分别是T1CC0L和T1CC0H)来控制Timer1的定时周期。注意,在编程的时候需要先写入T1CC0L然后再写入T1CC0H,因为手册上提示在写入T1CC0H的时候将会把T1CC0L的数据一并存入Timer1内部。 Timer1的单位计数周期是由系统时钟和Timer1自身的分频系数决定的,系统时钟是32MHz的外部晶振,因此每1/32 us Timer1就会增加一个计数,为了方便计算,调整Timer1的分频系数为32分频,即将T1CTL中的DIV[1:0]设置为2。 Timer1如何去触发ADC呢?这里有两种方法:1、配置Timer1的中断,在“比较捕获通道0中断”中去软件触发ADC的采样;2、配置ADC的触发源为Timer1的“比较捕获通道0”,这样在Timer1的“比较捕获”事件触发的时候自动触发ADC的转换。两种方案都可以,但后者明显更具有优势,大大降低了进中断的概率。本实例也以后者为例,Timer初始化如下: void Timer1_Init(unsigned short TimerPeriod) { T1CNTL = 0; T1CNTH = 0; TimerPeriod--; T1CC0L= TimerPeriod % 256; T1CC0H= TimerPeriod / 256; //默认采样周期 = 1ms T1CTL = 0x0A; //模计数方式 32分频 T1CCTL0 = 0x1C; //CH0比较输出 } 二、ADC配置 ADC这部分的配置比较简单,由于我们需要在一次触发的时候连续转换4个通道,因此需要让ADC工作在序列转换的模式下。寄存器ADCCON2是用来配置序列转换的,寄存器ADCCON3是用来配置单个转换的,明显的,这里不需要配置ADCCON3。 首先配置ADCCON1,这里只要关心STSEL[1:0],一般情况下都是配置为3,也就是通过软件置位ADCCON1.ST来触发一次转换(上面如果定时器使用中断方式,则可以配置为这种方式,然后在Timer1中断中置位ADCCON1.ST即可),而这里使用定时器自动触发就不同了,这部分需要写2,即“定时器1通道0比较事件”。 然后配置ADCCON2,SREF[1:0]选择参考电压,SDIV[1:0]用来设置分辨率,这里需要设置为最大,4个通道的转换在12位精度下大约需要500多us的时间。然后关键的来了,SCH[3:0]用来配置序列通道,这里不讨论差分通道的情况,只关注AIN0-AIN7独立情况,SCH[3:0]的低3位用来配置ADC的序列转换模式中最后一个被转换的通道,比如写入的是0101那么ADC将会从AIN0开始转换,AIN0转换完成后转换AIN1,以此类推,最后转换到AIN5结束,一次触发就完成了6个通道的转换。要注意的是,这是在IO口对应的模拟功能都打开时候也就是APCFG对应位为1时的情况,如果某个或某几个IO的APCFG寄存器对应位为0,那么ADC在进行一次序列转换的时候将会跳过那些通道而不进行转换,这样的设计将为不连续的通道转换提供了方便,比如需要转换AIN1/AIN4/AIN5/AIN7这4个通道,那么可以把SCH[3:0]配置为0111,然后APCFG配置为10110010=0xB2。 此时,ADC已经可以在定时器的周期触发下完成通道的转换工作了,下面就是如何收集转换完成的数据了。 请注意,当ADC配置为序列转换通道后,这部分的数据将不会出现在ADC结果寄存器(ADCH和ADCL)上,而是直接通知DMA来处理,因此,下面我们必须对DMA进行配置。 三、DMA配置 CC2541的DMA需要有一个单独的配置结构体,这部分的描述在数据手册中有详细说明,为了配置方便,我们需要建立一个结构体去描述DMA的行为: #pragma bitfields=reversed static struct { unsigned char SRCADDR_H; //DMA通道源地址 高字节 unsigned char SRCADDR_L; //DMA通道源地址 低字节 unsigned char DESTADDR_H; //DMA通道目的地址 高字节 unsigned char DESTADDR_L; //DMA通道目的地址 低字节 unsigned char VLEN : 3; //可变长传输模式 unsigned char LEN_H : 5; //DMA通道传送长度 高位 unsigned char LEN_L; //DMA通道传送长度 低位 unsigned char WORDSIZE : 1; //每个DMA传送大小 unsigned char TMODE : 2; //DMA通道传送模式 unsigned char TRIG : 5; //DMA触发选择 unsigned char SRCINC : 2; //源地址递增方式 unsigned char DESTINC : 2; //目的地址递增方式 unsigned char IRQMASK : 1; //DMA通道中断使能 unsigned char M8 : 1; //采用VLEN的第8位作为传送单位长度 unsigned char PRIORITY : 2; //DMA优先级 }ADC_DMA_Config; #pragma bitfields=default 开头和结束的两个编译指令用来提示编译器重新定义大小端编译方式,因为数据手册上提示DMA描述符遵循大端约定。 以上代码中的注释段介绍了每个部分的基本作用这里就不赘述了。 配置寄存器就相对简单了很多,这里我们使用的是DMA的通道0。 首先为了保险起见,先将DMAARM寄存器中的ABORT和DMAARM0置位,用来中止DMA0的工作状态,方便我们配置它(DMA工作的时候是无法配置一些相关寄存器的)。 然后我们队上面的结构体进行赋值,配置DMA工作在对应的模式下,这里需要注意的是: 1、SRCADDR_H和SRCADDR_L是源数据的地址,这里应该要写入ADC的结果寄存器的地址,然而ADC的结果寄存器定义在sfr段,是无法用&符号操作的。通过查看手册和相关头文件,发现CC2541为ADC的结果寄存器进行了XDATA段的映射,定义到了0x70BA的位置(详细参看ioCC2541.h的第574行:#define X_ADCL XREG( 0x70BA ) /* ADC Data Low) 2、VLEN即可变长传输模式并没有用到 3、LEN_H和LEN_L为传输数据的个数而不是具体多少个字节 4、WORDSIZE为单个传输的数据是8位的还是16位的,和上面的配置整体决定了传输的数据量 5、SRCINC写0表示源地址是不动的 6、DESTINC写1表示每次增加一个单位数据而不是1个字节,如果WORDSIZE配置为16位则每次增加2地址。这两个参数还可以配置为增加2个数据单位或者倒着减少一个数据单位 7、IRQMASK需要打开来使能DMA中断,当然也可以用查询但是效率太低 配置完成后需要把这个配置结构体的地址存到DMA的配置地址寄存器中去(像这里我们用到DMA通道0那么就是DMA0CFGH和DMA0CFGL)。使用DMA通道0和DMA通道1时是没有问题的,但是如果使用的是其他的通道,DMA的寄存器中并没有类似DMA2CFGH、DMA3CFGH这样的寄存器,所以不能直接配置。通过查看数据手册可以知道从2通道开始的配置地址都是在通道1配置地址之后延伸的,也就是说通道1、2、3、4的配置结构体的地址必须连续。 写入配置地址寄存器后我们还需要等待9x个时钟周期(x代表配置的DMA通道数量),DMA控制器需要用这些时间来初始化内部的配置寄存器。 完成写入后就可以将DMAARM寄存器中的DMAARMx(x代表要激活工作的通道)置位。 当然了,我们还需要打开DMA的总中断(DMAIE = 1;),在系统的中断寄存器中。 这时候DMA就开始正式工作了,ADC和DMA配置的代码如下: #define _NOP asm("NOP") void ADC_DMA_Init(void) { //ADC配置 ADCCON1 &= ~(0xF0); ADCCON1 |= 0x20; //T1CCR0触发 ADCCON2 = 0xB3; //AVDD5参考 + 512抽取率 + AIN0-AIN3输入 APCFG = 0x0F; //低4位打开ADC功能 0-3 //DMA配置 DMAARM |= 0x81; //先终止DMA ch0 //先配置DMA配置数据结构 ADC_DMA_Config.PRIORITY = 0x01; //优先级 ADC_DMA_Config.M8 = 0; ADC_DMA_Config.IRQMASK = 1; //使能DMA中断 ADC_DMA_Config.DESTINC = 0x01; //目标地址每次累加一个单位 ADC_DMA_Config.SRCINC = 0x00; //源地址是寄存器不需要递增 ADC_DMA_Config.TRIG = 20; //一个ADC序列完成触发DMA ADC_DMA_Config.TMODE = 0x02; //DMA循环触发模式,完成任务自动重新加载配置 ADC_DMA_Config.WORDSIZE = 1; //每次传送2个字节(1个字) ADC_DMA_Config.LEN_H = 0x00; ADC_DMA_Config.LEN_L = 40; //每次任务传输40个字 4通道X10个样本 ADC_DMA_Config.VLEN = 0x00; ADC_DMA_Config.DESTADDR_L = (uint16)DataBuffer2 & 0x00FF;//DMA目的地址 ADC_DMA_Config.DESTADDR_H = (uint16)DataBuffer2 >> 8; //DMA目的地址 ADC_DMA_Config.SRCADDR_L = (uint16)(&(X_ADCL))&0x00FF; //ADC转换寄存器的地址 ADC_DMA_Config.SRCADDR_H = (uint16)(&(X_ADCL)) >> 8; //将DMA配置数据结构的地址写入DMA配置寄存器 DMA0CFGL = ((uint32)&ADC_DMA_Config) & 0x00FF; DMA0CFGH = ((uint32)&ADC_DMA_Config) >> 8; _NOP; _NOP; _NOP; _NOP; _NOP; _NOP; _NOP; _NOP; _NOP; DMAARM |= 0x01; //启动DMA通道0的工作状态 DMAIE = 1; //开启DMA中断 } 四、DMA中断配置 DMA配置完成后,开启定时器,等待DMA中断的到来,IAR中断的写法不在赘述,这里要注意的是,在DMA中断中,不但需要清空DMAIRQ中对应的通道中断位,还要清空系统中断寄存器中DMAIF这一位,否则将会永远死在中断里面,代码如下: #pragma vector = DMA_VECTOR __interrupt void DMA_ISR(void) //DMA中断大约10ms触发一次,中断时间约260us { unsigned char i; DMAIRQ = 0x00; DMAIF = 0; for(i=0;i<10;i++) //数据提取搬运 { DataBuffer[0] = DataBuffer2[i*4 ] > 0 ? (DataBuffer2[i*4 ]>>2) : 0; DataBuffer[1] = DataBuffer2[i*4+1] > 0 ? (DataBuffer2[i*4+1]>>2) : 0; DataBuffer[2] = DataBuffer2[i*4+2] > 0 ? (DataBuffer2[i*4+2]>>2) : 0; DataBuffer[3] = DataBuffer2[i*4+3] > 0 ? (DataBuffer2[i*4+3]>>2) : 0; } } DMA自动传送过来的数据还不能直接用,需要进行简单的处理,ADC出来的结果可能是负值,所以需要将这些值进行修正(直接为0),然后得到的结果最低的2位永远是0没有用,所以整体向右移2位,注意这里所有的变量需要声明为singned型,否则会出错。 上面这部分的计算量大概需要260us的时间,结合上面4个通道转换需要500us左右的时间,是不会出现数据冲突的情况的。 至此,只要我们控制Timer1的启动和停止就可以控制ADC进行固定采样率的采样和转换了,例如本实例中,数据将会存在DataBuffer中,这是一个4X10的数组,代表4个通道的数据,每个通道10个连续采样点。可以通过简单修改参数改变转换的通道数量和数据长度以及采样率、精度等参数以适应不同的实际应用需要。 对于后续的数据处理和传输,跑协议栈的可以利用setevent函数触发一个消息给上层Task去处理,避免CPU长时间在中断中运行;跑裸机的则可以设置相关的标志位,在主函数或相关活动函数中进行后续处理。 以上代码已在(CC2541 BLE TI-Stack1.3.2 )+ (IAR for 8051-8.10)上测试通过,并不会干扰协议栈的正常运行。 |
|
相关推荐 |
|
只有小组成员才能发言,加入小组>>
物联网工程师必备:怎么选择不同的无线连接技术,本指南帮你忙!
3270 浏览 1 评论
【DFRobot TinkerNode NB-IoT 物联网开发板试用连载】WIFI功能测试
3919 浏览 0 评论
【DFRobot TinkerNode NB-IoT 物联网开发板试用连载】Arduino的替代SublimeText3+STino
3427 浏览 0 评论
使用端口扩展器轻松高效地向IIoT端点添加具有成本效益的子节点
3979 浏览 1 评论
20652 浏览 11 评论
模组有时候复位重启后输出日志为“REBOOT_CAUSE_SECURITY_PMU_POWER_ON_RESET”的原因?
762浏览 2评论
949浏览 2评论
974浏览 1评论
1092浏览 1评论
368浏览 1评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-2 17:16 , Processed in 0.742623 second(s), Total 39, Slave 34 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号