本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949
配套资料获取:https://renesas-docs.100ask.net
瑞萨MCU零基础入门系列教程汇总: https://blog.csdn.net/qq_35181236/article/details/132779862
本章目标
自然界的信号几乎都是模拟信号,比如光亮、温度、压力、声音,而为了方便存储、处理,计算机里面都是数字的0/1信号,将模拟信号(连续信号)转换为数字信号(离散信号)的器件就叫模数转换器(Analog-to-Digital Converter,ADC)。
按原理可分为:并行比较型A/D转换器(FLASH ADC)、逐次比较型A/D转换器(SAR ADC)和双积分式A/D转换器(Double Integral ADC)。
A/D转换过程通常为4步:采样、保持、量化和编码。如下图所示,
一个连续的电压信号ui(t)通过一个由方波CPs控制的开关S之后施加到电容C上,由于电容两端的电压不会突变,可知在S断开时C将维持ui(t)在开关断开瞬间的电压一段时间,直到开关S再次打开。这样,一个模拟的电压信号就转换成了采样展宽信号us(t),其中CPs的频率就是采样频率fs。最后,由ADC的数字编码电路将采样展宽信号us(t)转换成n位的数字量dn-1:d0并输出。
采样是对模拟信号周期性地抽取样值,使模拟信号转化为时间上离散的脉冲信号。采样频率(fS)越高,采样越密集,采样值越多,也就越接近模拟信号。为确保采样后的信号能够还原模拟信号,根据香农-奈奎斯特(Shannon & Nyquist)采样定理,采样频率必须大于等于2倍输入模拟信号的最高截止频率(fImax):
ADC的主要有三个性能指标:分辨率、转换时间和转换精度。
分辨率:又称为转换精度,指ADC能分辨的最小电压,通常使用二进制有效位表示,反应了ADC对输入模拟量微小变化的分辨能力。当最大输入电压一定时,位数越多,量化单位越小,误差越小,分辨率越高。比如一个12位的ADC,参考电压为3.3V,则其能分辨的最小电压为:
转换时间:其倒数为转换速率,指ADC从控制信号到来开始,到输出端得到稳定的数字信号所经历的时间。转换时间通常与ADC类型有关,双积分型ADC的转换时间一般为几十毫秒,属于低速ADC;逐次逼近型ADC的转换时间一般为几十微妙,属于中速ADC;并联比较型ADC的转换时间一般为几十纳秒,属于高速ADC。
转换精度:指ADC输出的数字量所表示的模拟值与实际输入的模拟量之间的偏差,通常为1个或半个最小数字量的模拟变化量,表示为1LSB或1/2LSB。
ADC的时钟源是PCLKC,在RA6M5的用户手册中对于PCLKC的时钟描述如下图所示:
PCLKC总线时钟最高能到50MHz,也就是说ADC12的转换时钟最高是50MHz。本书所有的实验都是在CPU支持的最高时钟下运行的,因而对于本章的ADC实验而言,其时钟都是50MHz。以50Mhz的ADC时钟而言,其部分硬件特性见下表:
瑞萨RA6M5芯片有2个逐次逼近型硬件ADC单元ADC0和ADC1,两个ADC共有多达26路采样通道(AN000AN010,AN012,AN013,AN100AN102,AN116~AN128),支持的采样有效位有8-bit,10-bit和12-bit。在阻抗为400Ω、时钟为50MHz情况下,采样转换时间最快为0.4us,即25MHz的采样率。
ADC输入范围为:VREFL-≤VIN≤VREFH+。RA6M5的参考电压是由VREFL、VREFH、AVCC0、AVSS0这四个外部引脚决定,且每个单元可以设置不同的参考电压,具体可以通过设置不同通道的VREFL、VREFH进行改变。
在设计原理图的时候一般把AVSS0和VREFL接地,把AVCC0和VREFH接3V3,得到ADC的输入电压范围为:0~3.3V。
如果采用12bit ADC是,那么12位满量程对应的就是3.3V,12位满量程对应的数字值是:2^12 = 4096。数值0对应的就是0V。如果转换后的数字量数值为n,n对应的模拟电压为v,那么:
瑞萨RA6M5的ADC有3种工作模式:
对于单次扫描模式和连续扫描模式,都会从最小的扫描通道开始从低到高进行A/D转换。如果开启了自诊断模式,在每次扫描开始时会执行一次转换,它会在三个参考电压中选择一个来转换。每一种转换模式都有着它的优点和缺点,需要通过实际需求来选择。
在单次扫描下,一次扫描一个或多个指定通道。在单次扫描模式转换期间,可以通过ADST为来判断ADC是否处在工作状态,在ADC转换的期间ADST为将一直保持为1,当所有选定通道的ADST转换完成时,将自动设置为0。
在连续扫描下,一个或多个指定的通道被重复扫描,直到软件设置寄存器ADCSR.ADST位为0。
将所选择的模拟输入通道分为A组和B组,然后按组对组内的模拟输入通道进行一次A/D转换。A、B组可独立选择扫描启动条件,可独立启动A、B组的A/D转换。
触发ADC开启转换的触发源有多种,主要分为三类:
如果使用ELC连接其他外设同步触发AD采样的话,那么触发源就多种多样了。可以通过定时器计数溢出触发、通信结束信号触发等等。使用哪一种触发方式取决于实际的项目需求。
根据瑞萨RA6M5的用户手册提供的时间转换图:
在RASC配置界面的“Pins”中的“Peripherals”中找到“Analog:ADC”,根据硬件设计的AD引脚决定选择“ADC0”还是“ADC1”。本书以ADC0的通道5为例,使用的引脚是P005,如下图所示;
在FSP配置界面的Stacks中,可以添加两类ADC的Stack模块:ADC和ADC-DMAC Integration。ADC-DMAC是在ADC的基础上再增添DMA功能,在本书下一小节会讲到如何配置。
添加步骤如下图所示:
ADC Module General的参数详情见下表:
配置ADC的采样通道以及窗口电压比较等,参数描述如下表:
配置ADC采样的触发源、中断使能优先级和中断回调函数名等,参数描述如下表:
使能与否ADC的环形缓冲区。
添加步骤如下图所示:
会得到这样一个ADC Stack模块:
ADC-DMAC中关于ADC Module的配置中,以下这些参数已被固定、不可更改:
如下图所示:
3.配置DMAC Transfer
ADC-DMAC中关于DMAC的配置有许多也是固定配置不可更改的,允许用户修改的参数如下:
如下图所示:
在多数情况下,使用周期性触发的定时器中断来触发ADC的采样。
对于瑞萨RA6M5的GPT模块配置和ELC模块配置在本书前面的章节已经介绍过,此处不再赘述。
ADC的引脚配置信息会在pin_data.c的g_bsp_pin_cfg_data[]数组中生成,代码如下:
const ioport_pin_cfg_t g_bsp_pin_cfg_data[] = {
{.pin = BSP_IO_PORT_00_PIN_05,
.pin_cfg = ((uint32_t) IOPORT_CFG_ANALOG_ENABLE)
},
......(省略内容)
};
如此处将P005复用为IOPORT_CFG_ANALOG_ENABLE,也就是使能了P005的模拟量输入功能。
ADC设备对象会在hal_data.c中生成,名为g_adc,代码如下:
const adc_instance_t g_adc5 =
{
.p_ctrl = &g_adc5_ctrl,
.p_cfg = &g_adc5_cfg,
.p_channel_cfg = &g_adc5_channel_cfg,
.p_api = &g_adc_on_adc
};
typedef struct
{
R_ADC0_Type * p_reg; // Base register for this unit
adc_cfg_t const * p_cfg;
uint32_t opened; // Boolean to verify that the Unit has been initialized
uint32_t initialized; // Initialized status of ADC
uint32_t scan_mask; // Scan mask used for Normal scan
uint16_t scan_start_adcsr;
void (* p_callback)(adc_callback_args_t *);
adc_callback_args_t * p_callback_memory;
/* Pointer to context to be passed into callback function */
void const * p_context;
} adc_instance_ctrl_t;
typedef struct st_adc_cfg
{
uint16_t unit; ///< ADC unit to be used
adc_mode_t mode; ///< ADC operation mode
adc_resolution_t resolution; ///< ADC resolution
adc_alignment_t alignment; ///< Specify left or right alignment; ignored if addition used
adc_trigger_t trigger; ///< Default and Group A trigger source
IRQn_Type scan_end_irq; ///< Scan end IRQ number
IRQn_Type scan_end_b_irq; ///< Scan end group B IRQ number
uint8_t scan_end_ipl; ///< Scan end interrupt priority
uint8_t scan_end_b_ipl; ///< Scan end group B interrupt priority
void (* p_callback)(adc_callback_args_t * p_args); ///< Callback function; set to NULL for none
void const * p_context; ///< Placeholder for user data. Passed to the user callback in @ref adc_callback_args_t.
void const * p_extend; ///< Extension parameter for hardware specific settings
} adc_cfg_t;
以软件触发采样为例,g_adc_cfg成员的赋值如下:
const adc_cfg_t g_adc5_cfg =
{
.unit = 0,
.mode = ADC_MODE_SINGLE_SCAN,
.resolution = ADC_RESOLUTION_12_BIT,
.alignment = (adc_alignment_t) ADC_ALIGNMENT_RIGHT,
.trigger = ADC_TRIGGER_SOFTWARE,
.p_callback = NULL,
....(省略内容)
};
typedef struct st_adc_channel_cfg
{
uint32_t scan_mask; ///< Channels/bits: bit 0 is ch0; bit 15 is ch15.
uint32_t scan_mask_group_b; ///< Valid for group modes.
uint32_t add_mask; ///< Valid if add enabled in Open().
adc_window_cfg_t * p_window_cfg; ///< Pointer to Window Compare configuration
adc_group_a_t priority_group_a; ///< Valid for group modes.
uint8_t sample_hold_mask; ///< Channels/bits 0-2.
uint8_t sample_hold_states; ///< Number of states to be used for sample and hold. Affects channels 0-2.
} adc_channel_cfg_t;
以软件触发采样为例,对于通道5,它的配置参数如下:
const adc_channel_cfg_t g_adc5_channel_cfg =
{
.scan_mask = ADC_MASK_CHANNEL_5 | 0,
.scan_mask_group_b = 0,
.priority_group_a = ADC_GROUP_A_PRIORITY_OFF,
.add_mask = 0,
.sample_hold_mask = 0,
.sample_hold_states = 24,
......(省略内容)
};
ADC的API接口,在结构体adc_api_t中声明了,代码如下:
typedef struct st_adc_api
{
fsp_err_t (* open)(adc_ctrl_t * const p_ctrl, adc_cfg_t const * const p_cfg);
fsp_err_t (* scanCfg)(adc_ctrl_t * const p_ctrl, void const * const p_extend);
fsp_err_t (* scanStart)(adc_ctrl_t * const p_ctrl);
fsp_err_t (* scanGroupStart)(adc_ctrl_t * p_ctrl, adc_group_mask_t group_mask);
fsp_err_t (* scanStop)(adc_ctrl_t * const p_ctrl);
fsp_err_t (* scanStatusGet)(adc_ctrl_t * const p_ctrl, adc_status_t * p_status);
fsp_err_t (* read)(adc_ctrl_t * const p_ctrl,
adc_channel_t const reg_id, uint16_t * const p_data
fsp_err_t (* read32)(adc_ctrl_t * const p_ctrl,
adc_channel_t const reg_id, uint32_t * const p_data);
fsp_err_t (* calibrate)(adc_ctrl_t * const p_ctrl, void const * p_extend);
fsp_err_t (* offsetSet)(adc_ctrl_t * const p_ctrl,
adc_channel_t const reg_id, int32_t const offset);
fsp_err_t (* callbackSet)(adc_ctrl_t * const p_api_ctrl,
void (* p_callback)(adc_callback_args_t *),
void const * const p_context,
adc_callback_args_t * const p_callback_memory);
fsp_err_t (* close)(adc_ctrl_t * const p_ctrl);
fsp_err_t (* infoGet)(adc_ctrl_t * const p_ctrl, adc_info_t * const p_adc_info);
} adc_api_t;
在r_adc.c中实现了一个adc_api_t结构体,代码如下:
const adc_api_t g_adc_on_adc =
{
.open = R_ADC_Open,
.scanCfg = R_ADC_ScanCfg,
.infoGet = R_ADC_InfoGet,
.scanStart = R_ADC_ScanStart,
.scanGroupStart = R_ADC_ScanGroupStart,
.scanStop = R_ADC_ScanStop,
.scanStatusGet = R_ADC_StatusGet,
.read = R_ADC_Read,
.read32 = R_ADC_Read32,
.close = R_ADC_Close,
.calibrate = R_ADC_Calibrate,
.offsetSet = R_ADC_OffsetSet,
.callbackSet = R_ADC_CallbackSet,
};
接下来就来了解下这些函数及其用法。
fsp_err_t (* open)(adc_ctrl_t * const p_ctrl, adc_cfg_t const * const p_cfg);
open函数初始化ADC,比如根据p_cfg来配置ADC的寄存器,设置中断回调函数等。但是这个函数没有完成对采样通道的初始化,采样通道的初始化需要调用scanCfg函数。
用户可以参考以下代码初始化ADC:
fsp_err_t err = g_adc5.p_api->open(g_adc5.p_ctrl, g_adc5.p_cfg);
assert(FSP_SUCCESS == err);
fsp_err_t (* close)(adc_ctrl_t * const p_ctrl);
调用close函数就是改变ADC的p-ctrl中代表ADC状态的某些成员的值,并且会关闭ADC的触发源以及停止采样。
fsp_err_t (* scanCfg)(adc_ctrl_t * const p_ctrl, void const * const p_extend);
在调用open函数初始化了ADC后,还需要调用scanCfg配置指定的采样通道。用户在调用此函数时,第二个参数传入g_adc的p_channel_cfg成员,例如:
err = g_adc5.p_api->scanCfg(g_adc5.p_ctrl, g_adc5.p_channel_cfg);
assert(FSP_SUCCESS == err);
fsp_err_t (* scanStart)(adc_ctrl_t * const p_ctrl);
fsp_err_t (* scanGroupStart)(adc_ctrl_t * p_ctrl, adc_group_mask_t group_mask);
如果没有使用组采样模式的话,就是用scanStart函数软件启动ADC采样。
如果使用了组采样模式,那么就需要调用scanGroupStart来启动ADC的组采样,第2个参数是adc_group_mask_t类型,它表示ADC的采样组别,有如下取值:
typedef enum e_adc_group_mask
{
ADC_GROUP_MASK_NONE = 0x000, ///< Group Mask Unknown or None
ADC_GROUP_MASK_0 = 0x001, ///< Group Mask 0
ADC_GROUP_MASK_1 = 0x002, ///< Group Mask 1
……(省略内容)
ADC_GROUP_MASK_7 = 0x080, ///< Group Mask 7
ADC_GROUP_MASK_8 = 0x100, ///< Group Mask 8
ADC_GROUP_MASK_ALL = 0x1FF, ///< All Groups
} adc_group_mask_t;
以普通模式为例,用户可以参考以下代码启动ADC的单次扫描采样:
fsp_err_t err = g_adc5.p_api->scanStart(g_adc5.p_ctrl);
assert(FSP_SUCCESS == err);
fsp_err_t (* scanStop)(adc_ctrl_t * const p_ctrl);
fsp_err_t (* scanStatusGet)(adc_ctrl_t * const p_ctrl, adc_status_t * p_status);
同样的,软件中止ADC采样时,也分为普通模式和组采样模式。不同的模式有各自的中止函数。
fsp_err_t (* read)(adc_ctrl_t * const p_ctrl,
adc_channel_t const reg_id,
uint16_t * const p_data);
fsp_err_t (* read32)(adc_ctrl_t * const p_ctrl,
adc_channel_t const reg_id,
uint32_t * const p_data);
用户通过read函数直接读取到ADC采样到的12bit的有效值。也可以调用read32函数读出采样值并转化为32bit的数据。
以直接读取12bit有效数据为例,用户可以参考以下代码读取ADC数据:
err = g_adc5.p_api->read(g_adc5.p_ctrl, ADC_CHANNEL_5, &value[i]);
assert(FSP_SUCCESS == err);
fsp_err_t (* scanStatusGet)(adc_ctrl_t * const p_ctrl, adc_status_t * p_status);
此函数能够获取到ADC的运行状态,有如下状态:
/** ADC states. */
typedef enum e_adc_state
{
ADC_STATE_IDLE = 0, ///< ADC is idle
ADC_STATE_SCAN_IN_PROGRESS = 1, ///< ADC scan in progress
} adc_state_t;
/** ADC status. */
typedef struct st_adc_status
{
adc_state_t state; ///< Current state
} adc_status_t;
如果用户采用软件触发采样的话,可以利用此函数判断ADC是否采样完成,例如:
while (ADC_STATE_SCAN_IN_PROGRESS == status.state)
{
g_adc5.p_api->scanStatusGet(g_adc5.p_ctrl, &status);
}
让用户学会使用瑞萨FSP ADC库函数驱动ADC外设,采样外部模拟电压。
本书配套的开发板板载没有ADC采样电路,用户使用具备ADC功能的引脚,用它来连接外部模拟信号。本书使用的是P005引脚,它对应ADC0的通道5。
1.ADC设备抽象
基于面向对象的编程思想,对ADC设备抽象出了一个结构体类型:
typedef struct ADCDev{
char *name;
unsigned short channel;
int (*Init)(struct ADCDev *ptDev);
int (*Read)(struct ADCDev *ptDev, unsigned short *value, unsigned short num);
}ADCDevTypeDef;
用户需要在ADC的驱动代码中构造ADCDev结构体,代码如下:
static int ADCDrvInit(struct ADCDev *ptDev);
static int ADCDrvRead(struct ADCDev *ptDev, unsigned short *value, unsigned short num);
static struct ADCDev gAdcDev = {
.name = "ADC0",
.channel = 5,
.Init = ADCDrvInit,
.Read = ADCDrvRead
};
并向上层调用者提供获取ADCDev的接口:
struct ADCDev *ADCGetDevice(void)
{
return &gAdcDev;
}
初始化ADC时,既要初始化ADC,也要初始化ADC里的某个通道。代码如下:
static int ADCDrvInit(struct ADCDev *ptDev)
{
if(NULL == ptDev) return -1;
if(5 == ptDev->channel)
{
fsp_err_t err = g_adc5.p_api->open(g_adc5.p_ctrl, g_adc5.p_cfg);
assert(FSP_SUCCESS == err);
err = g_adc5.p_api->scanCfg(g_adc5.p_ctrl, g_adc5.p_channel_cfg);
assert(FSP_SUCCESS == err);
}
return 0;
}
通过获取ADC的状态,来判断ADC当前是否正在进行采样转换,进而封装出来一个状态等待函数:
01 static void ADCWaitConvCplt(void)
02 {
03 adc_status_t status;
04 status.state = ADC_STATE_SCAN_IN_PROGRESS;
05
06 while (ADC_STATE_SCAN_IN_PROGRESS == status.state)
07 {
08 g_adc5.p_api->scanStatusGet(g_adc5.p_ctrl, &status);
09 }
10 }
用户使用软件开启ADC采样后,可以调用此函数等待采样转换完成。
本实验将ADC的开启和数据读取封装到了一个函数中,来开启指定通道的ADC采样以及等待采样完成,并读取、返回数据:
static int ADCDrvRead(struct ADCDev *ptDev, unsigned short *value, unsigned short num)
{
if(NULL == ptDev || NULL == value || num <= 0) return -1;
if(5 == ptDev->channel)
{
for(uint16_t i=0; i<num; i++)
{
fsp_err_t err = g_adc5.p_api->scanStart(g_adc5.p_ctrl);
assert(FSP_SUCCESS == err);
ADCWaitConvCplt();
err = g_adc5.p_api->read(g_adc5.p_ctrl, ADC_CHANNEL_5, &value[i]);
assert(FSP_SUCCESS == err);
}
}
return 0;
}
本次实验采用的测试方法是每隔1s连续采样4次,将采样出来的数字量通过换算得到模拟电压量,将其打印出来观测,测试代码如下:
void ADCAppTest(void)
{
SystickInit();
UARTDrvInit();
ADCDevTypeDef *ptAdcDev = ADCGetDevice();
if(NULL == ptAdcDev)
{
printf("Error. Not found ADC device!\r\n");
return;
}
printf("Success to find ADC device: %s-%d\r\n", ptAdcDev->name, ptAdcDev->channel);
ptAdcDev->Init(ptAdcDev);
while(1)
{
uint16_t buf[4] = {0};
ptAdcDev->Read(ptAdcDev, buf, 4);
printf("\r\nSample Result:\r\n\t");
for(uint16_t i=0; i<4; i++)
{
float value = (float)(buf[i]*3.3/4096);
printf("[%d] --> %f\r\n\t", i+1, value);
}
HAL_Delay(1000);
}
}
在hal_entry()中调用测试函数,将编译出来的二进制可执行文件烧录到板子上并运行,将P005接到某个不超过3.3V的直流电压上观察结果,会得到例如下图这样的打印信息:
对应ARM的Cortex-M内核,它对于数据的处理一套DSP库,集成到了ARM的CMSIS架构中,这些库包含以下处理算法:
对于CMSIS的DSP库,其内容非常的丰富,读者可以去Keil MDK的CMSIS DSP文档中心了解详细信息:https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html
CMSIS DSP库里面包含一个专门用于计算实数序列的FFT库,很多情况下,用户只需要计算实数序列即可。计算同样点数FFT的实数序列要比计算同样点数的虚数序列有速度上的优势。快速的rfft算法是基于混合基cfft算法实现的。
一个N点的实数序列FFT正变换采用下面的步骤实现:
由上面的框图可以看出,实数序列的FFT是先计算N/2个实数的CFFT,然后再重塑数据进行处理从而获得半个FFT频谱即可(利用了FFT变换后频谱的对称性)。
一个N点的实数序列FFT逆变换采用下面的步骤实现:
实数FFT支持浮点,Q31和Q15三种数据类型。以Q15和F32的RFFT DSP库函数为例。
此函数原型:
void arm_rfft_q15(const arm_rfft_instance_q15 * S, q15_t * pSrc, q15_t * pDst)
typedef struct
{
uint32_t fftLenReal; /**< length of the real FFT. */
uint8_t ifftFlagR; /**< flag that selects forward (ifftFlagR=0) or inverse (ifftFlagR=1) transform. */
uint8_t bitReverseFlagR; /**< flag that enables (bitReverseFlagR=1) or disables (bitReverseFlagR=0) bit reversal of output. */
uint32_t twidCoefRModifier; /**< twiddle coefficient modifier that supports different size FFTs with the same twiddle factor table. */
const q15_t *pTwiddleAReal; /**< points to the real twiddle factor table. */
const q15_t *pTwiddleBReal; /**< points to the imag twiddle factor table. */
#if defined(ARM_MATH_MVEI) && !defined(ARM_MATH_AUTOVECTORIZE)
arm_cfft_instance_q15 cfftInst;
#else
const arm_cfft_instance_q15 *pCfft; /**< points to the complex FFT instance. */
#endif
} arm_rfft_instance_q15;
使用arm_rfft_q15函数做FFT运算前需要先使用arm_rfft_instance_q15定义一个变量表明FFT的运算点数和类型:
arm_rfft_instance_q15 S = {.fftLenReal = 1024};
然后再调用arm_rfft_init_q15函数将此变量记录和初始化:
arm_rfft_init_q15(&S, 1024, 0, 0);
最后再调用arm_rfft_q15函数做FFT运算:
arm_rfft_q15(&S, (q15_t*)p_buf, (q15_t*)q_buf);
此函数原型:
void arm_rfft_f32(const arm_rfft_instance_f32 * S, float32_t * pSrc, float32_t * pDst)
用法和arm_rfft_q15非常类似:
2.1 使用arm_rfft_instance_f32定义一个变量表明FFT的运算点数和类型:
arm_rfft_instance_f32 S = {.fftLenReal = 1024};
2.2 然后再调用arm_rfft_init_f32函数将此变量记录和初始化:
arm_rfft_init_f32(&S, 1024, 0, 0);
2.3最后再调用arm_rfft_q15函数做FFT运算:
arm_rfft_f32(&S, (float32_t*)p_buf, (float32_t*)q_buf);
让用户学会使用RASC添加CMSIS DSP模块,并学会使用DMAC方式传输ADC的采样数据到内存中,了解定时器触发ADC采样的配置。
CMSIS DSP的添加步骤如下图所示:
参考本书前文《第21章 事件链接控制器ELC》。
本书配套的开发板板载没有ADC采样电路,用户使用具备ADC功能的引脚,用它来连接外部模拟信号。本书使用的是P005引脚,它对应ADC0的通道5。
本次实验依然需要抽象ADC设备,参考上一节。
在初始化函数中需要完成ADC、DMAC、ELC和定时器的初始化,并且使能DMA、使能ELC,代码如下:
static int ADCDrvInit(struct ADCDev *ptDev)
{
if(NULL == ptDev) return -1;
if(5 == ptDev->channel)
{
/* 打开ADC设备完成通用初始化 */
fsp_err_t err = g_adc5.p_api->open(g_adc5.p_ctrl, g_adc5.p_cfg);
assert(FSP_SUCCESS == err);
/* 配置ADC指令的通道完成初始化 */
err = g_adc5.p_api->scanCfg(g_adc5.p_ctrl, g_adc5.p_channel_cfg);
assert(FSP_SUCCESS == err);
/* 打开ELC设备完成初始化 */
err = g_elc.p_api->open(g_elc.p_ctrl, g_elc.p_cfg);
assert(FSP_SUCCESS == err);
/* 使能ELC的连接功能 */
err = g_elc.p_api->enable(g_elc.p_ctrl);
assert(FSP_SUCCESS == err);
/* 打开DMA设备完成初始化 */
err = g_transfer0.p_api->open(g_transfer0.p_ctrl, g_transfer0.p_cfg);
assert(FSP_SUCCESS == err);
/* 使能DMAC的ELC触发源 */
err = g_transfer0.p_api->enable(g_transfer0.p_ctrl);
assert(FSP_SUCCESS == err);
/* 打开定时器设备完成初始化 */
err = g_timer0.p_api->open(g_timer0.p_ctrl, g_timer0.p_cfg);
assert(FSP_SUCCESS == err);
/* 使能ADC的转换功能 */
err = g_adc5.p_api->scanStart(g_adc5.p_ctrl);
assert(FSP_SUCCESS == err);
}
return 0;
}
在RASC的配置中,DMAC中断的触发源是传输完所有的数据,因而只需要在中断回调函数中将传输完成标志赋值为true即可:
static volatile bool adc_sample_cplt = false;
void adc5_dma_callback(dmac_callback_args_t * p_args)
{
adc_sample_cplt = true;
}
DMAC传输完成就代表着ADC采样的完成,因而可以依靠DMAC中断回调函数中的标志位来判断ADC是否全部采样完成:
static void ADCWaitConvCplt(void)
{
while(!adc_sample_cplt);
adc_sample_cplt = false;
}
此函数里,先重新配置DMAC,再开启定时器,使用定时器来触发ADC转换。然后等待ADC完成后,关闭定时器。ADC的采样结果,通过DMA传输到buffer里。代码如下:
static int ADCDrvRead(struct ADCDev *ptDev, unsigned short *buffer, unsigned short num)
{
if(NULL == ptDev || NULL == buffer || num <= 0) return -1;
if(5 == ptDev->channel)
{
/* 每次采样前将DMAC的目的地址和传输个数重置 */
g_transfer0.p_cfg->p_info->p_dest = buffer;
g_transfer0.p_cfg->p_info->length = num;
fsp_err_t err = g_transfer0.p_api->reconfigure(g_transfer0.p_ctrl, g_transfer0.p_cfg->p_info);
assert(FSP_SUCCESS == err);
/* 开启定时器触发ADC采样 */
err = g_timer0.p_api->start(g_timer0.p_ctrl);
assert(FSP_SUCCESS == err);
ADCWaitConvCplt();
/* 采样结束后关闭定时器 */
err = g_timer0.p_api->stop(g_timer0.p_ctrl);
assert(FSP_SUCCESS == err);
}
return 0;
}
本次实验仅仅是对ADC采样的数据做了一次FFT运算,但是并没有对运算出来的结果做进一步的频谱分析。代码如下:
#include
uint16_t p_buf[1024] = {0};
uint16_t q_buf[1024] = {0};
void ADCAppTest(void)
{
SystickInit();
UARTDrvInit();
ADCDevTypeDef *ptAdcDev = ADCGetDevice();
if(NULL == ptAdcDev)
{
printf("Error. Not found ADC device!\r\n");
return;
}
printf("Success to find ADC device: %s-%d\r\n", ptAdcDev->name, ptAdcDev->channel);
ptAdcDev->Init(ptAdcDev);
arm_rfft_instance_q15 S = {.fftLenReal = 1024};
arm_rfft_init_q15(&S, 1024, 0, 0);
while(1)
{
ptAdcDev->Read(ptAdcDev, p_buf, 512);
arm_rfft_q15(&S, (q15_t*)p_buf, (q15_t*)q_buf);
HAL_Delay(1000);
}
}
本次实验没有结果输出,读者可以自行将FFT运算结果通过串口输出观察。