nrf52832 PPI、SAADC、FreeRTOS学习总结

本着学习的态度,本人总结了网上诸多相关内容的博客,由于时间较久远,出处不能一一列举,如有侵权,please feel free to let me know.
一、 PPI

  1. PPI原理
    PPI为nrf52832的可编程外设互连,可以提供一个硬件通道,将不同外设的事件和任务“连接”在一起,当事件产生时硬件自动触发事件“连接”的任务。PPI机制的设计,使得被"连接"的任务由硬件自动触发完成,省去了原本CPU需要参与的步骤。这一方面降低了CPU的负荷,另一方面保证了产生事件到执行任务的实时性。
  2. PPI的实现
    1. nRF52832共有32个通道(编号为031),其中,有12个通道(通道2031)已经被预编程,剩余20个通道(通道0~19)是用户可编程的;
    2. 每个PPI通道由3个端点寄存器组成,1个EEP(Event End-Point:事件端点)和2个TEP(Task End-Point:任务端点,次级任务端点)寄存器;
    3. 任务端点和次级任务端点同时被触发;
    4. 使用PPI连接外设事件和外设任务的时候,将外设事件寄存器的地址写入到PPI通道的EEP,将外设任务寄存器的地址写入到PPI通道的TEP,最后使能该PPI通道实现事件和任务间的连接;
  3. PPI组
    如果需要一个事件能同时触发多个任务或者一个任务能同时被多个事件触发,可以通过PPI组来实现。
  4. PPI寄存器
    PPI只有任务寄存器和通用寄存器,它没有事件寄存器,所以PPI产生不了事件。
    a. 任务寄存器
    1. CHG[n].EN(n=0~5): 使能PPI通道组n
    2. CHG[n].DIS(n=0~5): 禁用PPI通道组n
      b. 通用寄存器
    3. CHEN: 使能/禁用PPI通道
    4. CHENSET: 使能PPI通道
    5. CHENCLR: 禁用PPI通道
    6. CH[n].EEP(n=0~19): PPI通道n事件端点
    7. CH[n].TEP(n=0~19): PPI通道n任务端点
    8. CHGn: PPI通道组n
    9. FORK[n].TEP(n=0~31): PPI通道n次级任务端点
  5. PPI相关库函数
    1. nrf_drv_ppi_init(void);//初始化PPI模块
    2. nrfx_ppi_channel_alloc(nrf_ppi_channel_t * p_channel);//申请PPI通道
      PPI通道是由驱动来分配的,而不是应用程序指定的,当需要一个PPI通道的时候,调用nrf_drv_ppi_channel_alloc()函数,该函数会查找空闲的PPI通道,并将查找到的第一个PPI通道传递给函数的输入参数p_channel。
    3. nrfx_ppi_channel_assign(nrf_ppi_channel_t channel, uint32_t eep, uint32_t tep);//配置通道的EEP和TEP
      其中,eep为事件寄存器的地址,tep为任务寄存器的地址。地址的获得有两种方式:
      从芯片的应用指南中查到;通过调用外设的驱动函数来获得地址。
    4. nrfx_ppi_channel_fork_assign(nrf_ppi_channel_t channel, uint32_t fork_tep);//配置通道次级任务端点
    5. nrfx_ppi_group_alloc(nrf_ppi_channel_group_t * p_group);//申请PPI通道组
      6)__STATIC_INLINE nrfx_err_t nrfx_ppi_channel_include_in_group(nrf_ppi_channel_t channel, nrf_ppi_channel_group_t group)//向PPI通道组加入指定通道
    6. __STATIC_INLINE nrfx_err_t nrfx_ppi_channel_remove_from_group(nrf_ppi_channel_t channel, nrf_ppi_channel_group_t group)//从PPI通道组移除指定通道
    7. nrfx_ppi_channel_enable(nrf_ppi_channel_t channel);//使能PPI通道
      9)nrfx_ppi_channel_disable(nrf_ppi_channel_t channel);//禁用PPI通道
    8. nrfx_ppi_group_enable(nrf_ppi_channel_group_t group);//使能PPI通道组
    9. nrfx_ppi_group_disable(nrf_ppi_channel_group_t group);//禁用PPI通道组

二、 SAADC

  1. SAADC
    ADC 的结构和实现原理有多种方式,常见的 ADC 的类型有积分型、逐次逼近型、并行比较型/串并行型、Σ-Δ调制型等。nRF52832 集成的是逐次逼近型 ADC,称为 SAADC(Successive approximation analog-todigital converter)。SAADC 是利用二分法逐步比较,在有效精度范围内找到最接近输入模拟信号的数字量。由此可见,这种结构的 ADC 要完成一次转换,至少要比较 N 次,所以其转换速度较慢,同时电路结构也比较简单,功耗较低,适用于便携式、穿戴式等低功耗应用领域。
  2. nrf52832的SAADC特点
    1) SAADC支持8/10/12位ADC采集,使用过采样可达到14位分辨率;
    2) 具有8个通道,支持差分输入和单路输入。差分输入模式下,测量结果为两输入端口电压差的转换的有符号数值;
    3) 可以通过软件触发采样任务启动采样,也可以使用低功耗的 32.768kHz RTC 或更加精确1/16MHz 定时器通过 PPI 触发采样任务,从而使能 SAADC 具备非常灵活的采样频率;
    4) 支持单次模式和扫描模式:单次模式一次采样一个通道;扫描模式按照顺序采样一系列通道;
    5) 通过EasyDMA可直接将采样结果保存到RAM;
    6) 无需外部定时器可实现连续采样;
    7) 可配置通道输入负载电阻;
    8) 具备采样值门限检测功能
  3. SAADC的使用
    使用 ADC 时,通过 CH[n].PSELP 和 CH[n].PSELN 配置通道的模拟输入引脚,通过CH[n].CONFIG 配置 ADC 的参数,包括:模拟输入端口负载电阻、增益、参考电压、采样时间、单端或差分输入模式等。ADC 输出的采样取决与 CH[n].CONFIG 和 RESOLUTION 寄存器配置的参数,采样结果计算公式如下:
    采样结果 = {[V§ – V(N) ] * GAIN}/{REFERENCE * 2 ^(RESOLUTION - m)}
    其中,V( P):ADC 输入正极,V(N):ADC 输入负极,GAIN:CH[n].CONFIG 寄存器中设置的增益 GAIN,REFERENCE:参考电压,m:如果 ADC 配置为单端模式,m = 0,如果 ADC 配置为差分模式,m = 1。
    nRF52832 的 ADC 有 3 种工作模式:单次模式、连续模式和扫描模式。
  4. SAADC中的函数使用
    初始化:nrf_drv_saadc_init(nrf_drv_saadc_config_t const * p_config, nrf_drv_saadc_event_handler_t event_handler);//p_config:配置参数的结构体指针 event_handler:事件回调函数。
    ADC通道配置: nrf_drv_saadc_channel_init(uint8_t channel, nrf_saadc_channel_config_t const * const p_config);//channel:表示使用的ADC通道 p_config:该通道的配置结构体指针。在通道配置中我们可以配置参考电压,输入增益等,详情参考配置结构体。
    启动ADC转换:nrf_drv_saadc_sample();//启动一次ADC转换,该转换包括配置的所有ADC通道的转换。转换完成后,会自动在中断程序里面调用初始化时候配置的回调函数,并且会得到相关中断的事件的信息。
    绑定转换缓冲区:单通道:nrf_drv_adc_sample_convert(channel,value);多通道:nrf_drv_adc_buffer_convert(buffer,size); 其中,size 是buffer的长度,如果size是2,使能了一个通道,就是把该通道采集两次,每次都要调用nrf_drv_adc_sample();如果size是2,使能了两个通道(如input4和input5),则按照使能先后顺序,buffer[0]对应input4,buffer[1]对应input5;阻塞模式下,buffer满了之后才会调用callback。
    三、 FreeRTOS
  5. freeRTOS
    RTOS的运转需要一个滴答定时器周期性地产生中断来为系统提供时钟,通常是1毫秒或者10毫秒。对于低功耗应用场景,定时器会频繁地产生中断,使得MCU频繁被唤醒,导致功耗升高,无法满足项目需求。freeRTOS中引入了TICKLESS模式来实现低功耗应用。TICKLESS模式的原理是动态地改变系统时钟的中断频率,当没有任务运行时,保持MCU处于睡眠模式,减少不必要的时钟中断。
  6. 任务状态
    FreeRTOS中的任务永远处于以下四种状态的某一个:运行态、就绪态(处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行)、阻塞态(如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了函数vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临)和挂起态(像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数vTaskSuspend()和xTaskResume())。任务状态之间的转换关系如下图所示。
    nrf52832 PPI、SAADC、FreeRTOS学习总结_第1张图片
  7. 任务优先级
    每个任务都可以分配一个从0~(configMAX_PRIORITIES-1) 的优先级,configMAX_PRIORITIES 在文件FreeRTOSConfig.h 中有定义,如果所使用的硬件平台支持类似计算前导零这样的指令(可以通过该指令选择下一个要运行的任务,Cortex-M 处理器是支持该指令的),并且宏configUSE_PORT_OPTIMISED_TASK_SELECTION 也设置为了1,那么宏configMAX_PRIORITIES 不能超过32,也就是优先级不能超过32 级。其他情况下宏configMAX_PRIORITIES 可以为任意值,但是考虑到RAM 的消耗,宏configMAX_PRIORITIES最好设置为一个满足应用的最小值。
    优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为0。FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING 定义为1 的时候多个任务可以共用一个优先级,数量不限。默认情况下宏configUSE_TIME_SLICING 在文件FreeRTOS.h 中已经定义为1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。
  8. 函数及变量
    xTaskCreate(TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask )或xTaskCreateStatic( TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * const pvParameters, UBaseType_t uxPriority, StackType_t * const puxStackBuffer, StaticTask_t * const pxTaskBuffer ):创建任务,这两个函数的第一个参数pxTaskCode,就是这个任务的任务函数;uxPriority表示任务的优先级,最好设置成实际任务需要的最小值,以避免浪费。
    在freeRTOS中全局变量[xNextTaskUnblockTime]记录着下一个任务唤醒的时间,[xTickCount]记录着当前系统时间,当系统处于空闲任务时,使用[xNextTaskUnblockTime - xTickCount]计算出MCU可以睡眠的时长,将计算结果传给portSUPPRESS_TICKS_AND_SLEEP函数,这个函数在port_cmsis_systick.c中实现,该函数修改RTC的中断模式,然后使用WFE的方式让MCU进入睡眠模式,当MCU被事件唤醒之后,恢复RTC中断为之前的状态。
    创建任务的时候需要给任务指定堆栈,如果使用的函数xTaskCreate()创建任务(动态方法)的话那么任务堆栈就会由函数xTaskCreate()自动创建;如果使用函数xTaskCreateStatic()创建任务(静态方法)的话就需要程序员自行定义任务堆栈,然后堆栈首地址作为函数的参数puxStackBuffer 传递给函数。
    unsigned portBASE_TYPE uxTaskPriorityGet( xTaskHandle pxTask ):用于查询一个任务的优先级,pxTask被查询任务的句柄(目标任务)。
    vTaskPrioritySet( xTaskHandle pxTask, unsigned portBASE_TYPE uxNewPriority ):改变任务的优先级,pxTask为被修改优先级的任务句柄(即目标任务) ;uxNewPriority目标任务将被设置到哪个优先级上。如果设置的值超过了最大可用优先级(configMAX_PRIORITIES – 1),则会被自动封顶为最大值。常量 configMAX_PRIORITIES 是在 FreeRTOSConfig.h 头文件中设置的一个编译时选项。
    vTaskStartScheduler( void ) 开始调度一个任务执行,第一次调度时会自动创建一个空闲任务。
    vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) :用于实现一个固定执行周期的需求(当需要让任务以固定频率周期性执行的时候),pxPreviousWakeTime保存了任务上一次离开阻塞态(被唤醒)的时刻这个时刻被用作一个参考点来计算该任务下一次离开阻塞态的时刻;pxPreviousWakeTime 指向的变量值会在API 函数vTaskDelayUntil()调用过程中自动更新,应用程序除了该变量第一次初始化外(通过调用xTaskGetTickCount()初始化),通常都无需修改它的值;xTimeIncrement,此参数命名时同样是假定vTaskDelayUntil()用于实现某个任务以固定频率周期性执行 —— 这个频率就是由xTimeIncrement 指定的;xTimeIncrement 的单位是心跳周期,可以使用常量;portTICK_RATE_MS 将毫秒转换为心跳周期。
    vTaskDelete( TaskHandle_t xTaskToDelete ):删除一个任务 //xTaskToDelete
    要删除任务的句柄。
    taskYIELD: 调用此函数的任务转入就绪态,同时另一个任务转入运行态。
    uxTaskPriorityGet( xTaskHandle pxTask ):用于查询一个任务的优先级,pxTask被查询任务的句柄(目标任务)。

你可能感兴趣的:(nrf52832)