NRF52832 SAADC 多通道双缓冲的理解

为了实现ADC采样速度更快,可以使用双缓冲功能。先说下SAADC的工作模式

一、SAADC的工作模式

根据规格书SAADC共有4中工作模式:one shot、Continuous mode、Oversampling、Scan mode。
one shot:一次触发,只运行单个通道,采样一次。
Continous mode:持续触发模式,本质是通过timer定时去不断的one shot。
Oversampling:过采样,是为了提高信噪比,增加数据的准确性。缺点是采样速度变慢,数据变的平滑。
Scan mode:扫描模式,如果需要同时采集同个通道,则需要使用扫描模式。

以上模式,除了Oversampling是要设置寄存器(OVERSAMPLE)之外,其他都是根据代码不同来设置的。比如:若只调用ret_code_t nrf_drv_saadc_sample_convert(uint8_t channel, nrf_saadc_value_t * p_value)函数,则为one-shot。
若使能了多个ADC 通道,然后调用start任务,则会进入Scan mode。
若使能了SAMPLERATE.MODE为Timers,则会进入Continous mode。

二、双缓冲和工作模式的关系。

工作模式决定了ADC通道采样的方法。ADC采样需要有一个外部的触发才会开始。这种触发可以是code,可以是PPI+timer去触发。可以是Continous mode里面设置timer。触发后的ADC会结果放到寄存器RESULT.PTR所指向的RAM区域。说到这里,就可以引申到双缓冲了。规格书里说寄存器RESULT.PTR是支持双缓冲的。但是PTR只有一个,且只能存一个RAM地址。那是怎么实现两个缓冲区之间的切换的呢?根据开源的那部分代码分析。我认为这部分工作应该是softdevide完成了。下面是我猜测的分析。

NRF52832 SAADC 多通道双缓冲的理解_第1张图片传入缓冲区的函数是这个,函数会判断是否存在第一个缓冲区,如果存在,则会将新传进来的第二个缓冲区写到软件的m_cb.p_secondary_buffer位置。
在运行时,当完成了第一个缓冲区后,softdevice会将第二个缓冲区的指针复制到第一个缓冲区的指针,并更新RESULT.PTR寄存器。然后清空p_secondary_buffer。此函数还会让ADC start任务(在函数后面)。注意start任务不会让ADC自动的去采样,他是需要上面讲的,不停的去触发才会开始采样。start只是让ADC可以工作而已。

ret_code_t nrf_drv_saadc_buffer_convert(nrf_saadc_value_t * p_buffer, uint16_t size)
{
***
    if (m_cb.adc_state == NRF_SAADC_STATE_BUSY)  //如果ADC正在工作,说明至少第一个缓冲区是非NULL的,则需要将缓冲区放入第二个缓冲区
    {
        if ( m_cb.p_secondary_buffer)  //第二个缓冲区指针不为NULL,则会产生忙错误
        {
            nrf_saadc_int_enable(NRF_SAADC_INT_END);
            return NRF_ERROR_BUSY;
        }
        else
        {
            m_cb.p_secondary_buffer = p_buffer;
            m_cb.secondary_buffer_size = size;
****
            {
                while (nrf_saadc_event_check(NRF_SAADC_EVENT_STARTED) == 0);
                nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
                nrf_saadc_buffer_init(p_buffer, size);
            }
            nrf_saadc_int_enable(NRF_SAADC_INT_END);
            return NRF_SUCCESS;   //如果是存入第二个缓冲区,则return。
        }
    }
    nrf_saadc_int_enable(NRF_SAADC_INT_END);
    m_cb.adc_state = NRF_SAADC_STATE_BUSY;

    m_cb.p_buffer = p_buffer;  //将空间放入第一个缓冲区
    m_cb.buffer_size = size;
    m_cb.p_secondary_buffer = NULL;

******

    nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);//清除started事件
    nrf_saadc_task_trigger(NRF_SAADC_TASK_START); //开始start事件

    return NRF_SUCCESS;
}

三、双缓冲区具体工作流程。

NRF52832 SAADC 多通道双缓冲的理解_第2张图片
PTR指针指向某区域0x20000000,然后产生started事件,已经开始采样,将采样的通道以1数据放入0x20000000
采样的数据2放入0x20000002.填充满了,产生END事件,同时接着触发start任务之后硬件产生started事件,
后面又指定第二个缓冲区的地址0x20000020.则重复上面的动作并将数据存入第二个缓冲区。不停的轮替缓冲区。

四、软件的实现流程

NRF52832 SAADC 多通道双缓冲的理解_第3张图片初始化ADC通道
比如我这里初始化硬件的AIN2,AIN0,AIN4三个接口都同时初始化了。初始化使用默认参数的宏NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE来快速定义变量。

void saadc_init(void)
{
    ret_code_t err_code;
    nrf_saadc_channel_config_t channel_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
    err_code = nrf_drv_saadc_init(NULL, saadc_callback);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_channel_init(0, &channel_config);
    APP_ERROR_CHECK(err_code);
	channel_config.pin_p = NRF_SAADC_INPUT_AIN0;
	err_code = nrf_drv_saadc_channel_init(1, &channel_config);
    APP_ERROR_CHECK(err_code);
	channel_config.pin_p = NRF_SAADC_INPUT_AIN4;
	err_code = nrf_drv_saadc_channel_init(2, &channel_config);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0],SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1],SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
}

设置缓冲区
双缓冲的数据存放在二维数组里面。

#define SAMPLES_IN_BUFFER 3
static nrf_saadc_value_t       m_buffer_pool[2][SAMPLES_IN_BUFFER];

如下两个代码,为设置好两个缓冲区
err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0],SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1],SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);

实现ADC中断函数
因为ADC在1个缓冲区填充满之后,会产生DONE中断。所以取数据,设置新缓冲区动作都在这里完成。
这个代码,将当前存满数据的缓冲区设置为下一个缓冲区。然后对数据进行了自定义的业务操作(这部分需要快速完成,避免数据被冲掉)。

void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{      float  val; 
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {
        ret_code_t err_code;
     
        err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);

        int i;
        printf("ADC event number: %d\r\n",(int)m_adc_evt_counter);
        for (i = 0; i < SAMPLES_IN_BUFFER; i++)
        {
//            printf("%d\r\n", p_event->data.done.p_buffer[i]);
						  val = p_event->data.done.p_buffer[i] * 3.6 /1024;	
			      printf("Voltage = %.3fV\r\n", val);
        }
        m_adc_evt_counter++;
    }
}

五、常见问题

如果缓冲区的空间大于或小于ADC的通道数会怎样?
答:假设有3个通道,每个缓冲区只有2个。输出的数据会是这样的。
第一次输出:通道1数据 、通道2数据。
第二次输出:通道2数据 、通道3数据
第三次输出:通道3数据 、通道1数据
。。。。如此反复。

假设有3个通道,每个缓冲区只有5个。输出的数据会是这样的。
第一次输出:通道1数据 、通道2数据、通道3数据、通道1数据、通道2数据。
第二次输出:通道3数据 、通道1数据、通道2数据、通道3数据、通道1数据。
。。。。如此反复。

你可能感兴趣的:(单片机,arm)