stm32学习笔记(3)就绪表 优先级确定

uC/OS-II总是在已就绪的任务中选择一个任务来运行。为了了解系统中的任务哪些是就绪任务,uC/OS-II在系统初始化时间里了一个供就绪任务登记的表,整个表就叫做就绪任务表。

    1).8x8矩阵

    Jean J.Labrosse 在设计的时候,因为uc/os中每个任务的优先级都是不同的,这样就使得每个任务都有与之相对应的独一无二的优先级。因此,任务就绪状态就可以根据优先级别的数量建立一个表,通过这个表,调度器就可以决定下一次该哪个优先级的任务开始运行了。因为uC/OS-II任务最多只能是64个,Jean J.Labrosse 就把就绪表分成8*8的一个矩阵形表(table),这样就刚好能存放64个就绪的任务了。这样就有了下面的表格,里面的数字代表优先级。

    

这个表格就是OSRdyTbl[];按优先级将它分成了八组,每一行作为一组,每组八个任务,(也即按Y坐标分组)。

    uC/OS-II的就绪任务表从编程上面来看,实质上就是一个类型为INT8U的数组OSRdyTbl[ ]。其中的每一位对应一个任务。OSRdyTbl[]数组的一个元素就可以表示8个任务的就绪状态。为了方便对就绪表的查找,uC/OS-II又定义了一个数据类型为INT8U的变量OSRdyGrp,并使该变量的每一个位都对应OSRdyTbl[]的一个任务祖(即数组的一个元素)。如果某任组中有任务就绪,则在变量OSRdyGrp里把该任务组所对应的位置为1;否则置为0。

    2).下面来看看就绪表中两个变量OSRdyGrp和OSRdyTbl[],两者关系如下图:


这两个变量的对应关系是:OSRdyTbl[ n ] 中任何一位是1时,OSRdyGrp  的第n 位 是1。相当于OSRdyGrp是OSRdyTbl[]的数组下标索引。

      这个知识点要这么来理解:OSRdyGrp是一个有八位二进制数的数,它的每一位代表一个组。比如:OSRdyGrp的第0位代表了第一行0到7这八个优先级。第一位代表了8到15这八个优先级,以此类推。只要哪一组OSRdyTbl[n]中的任何一位置位(即哪一组中有任何一个任务进入就绪表),那么OSRdyGrp相应的位就置1。比如:第一行中优先级0到7,只要有一个任务进入就绪表,那么OSRdyGrp的第0位就会置1(有更多任务进入就绪表也会置1),表示这一行中有任务进入就绪表啦,所以我们说:OSRdyGrp等于是OSRdyTbl[]的数组下标索引。

3). 如何知道具体INT8U组OSRdyTbl[n]里面8个中间的哪一个任务就绪?

      开头我们已经说了,就绪表就是按优先级排成的矩阵,那么:由于优先级别是一个单字节的数字,而且其最大值不会超过63,即二进制形式的00111111,因此可把优先级看成是一个6位的二进制数。这样高3位(D5,D4,D3)来指明变量OSRdyGrp的具体数据位,并用来确定就绪表数组元素的下标。低3位(D2,D1,D0)来指明该数组元素的具体数据位。
      例如Prio=30的就绪任务,其二进制形式为00011110,其低6位为011110,于是D5,D4,D3为011,故任务在OSRdyTbl[3]上,因此OSRdyGrp的D3位置1,D2,D1,D0为110,故OSRdyTbl[3]上的D5位上要置1。


4).如何查表?

    

这就要看OSRdyTbl[]了。先来介绍一个掩码的知识:ucos里面有一个数组,叫OSMapTbl[],它来帮助我们查一个任务是否进入优先级。


具体如下:



OSMapTbl[]的下标


OSMapTbl[](即位掩码)


0


00000001


1


00000010


2


00000100


3


00001000


4


00010000


5


00100000


6


01000000


7


10000000


我们来看,一个任务优先级将它化成二进制数,它的低三位与它在哪一组没有任务关系!因为逢八才换组,而低三位最大才是七!但它能决定这个任务在一组的某个地方(即能决定X的值)而它的接下去三位与它在一组中的哪个位置没有任何关系!因为去掉低三位,接下去三位始终是八的倍数,始终指向组的首地址!但它能决定Y的值!这样,在一个矩阵中,X的值和Y的值都决定了,那么这个任务所在的位置就决定了。这样,使一个任务进入就绪状态和脱离就绪状态也就简单了。


其实不用这么麻烦,我们可以把表中的数看成是八进制的数,低三位看成是个位,接下来三位看成是进位,进位代表OSRdyGrp,个位代表OSRdyTbl[],个位无论怎么变,它都不会影响进位的位置,所以在哪一行只取决于OSRdyGrp,在哪一列只取决于OSRdyTbl[],口算也算出来了,呵呵。这样比较好理解一些。


这里可能大家有疑问:那不只取了优先级的六位吗?还有两位呢?这个不用着急,还有两位为零!因为最大才63,只需要用到六位,如果最高两位有任何一位为一的话,就会出错。所以我们就不用管最高两位了。


a. 使任务进入就绪状态的程序为:


OSRdyGrp             |= OSMapTbl[prio>>3];//确定它的行,置1   Prio的次高三位可以确定OSRdyGrp


OSRdyTbl[prio>>3]     |=OSMapTbl[prio&0x07];//确定它的列,置1   低三位可以确定OSRdyTbl[]

上面这两行代码还是很容易理解的:
先将prio右移3位,去掉X坐标占用的后三位,只取Y坐标,填入OSMapTbl[ ], 返回值与OSRdyGrp做位或,将OSRdyGrp对应位置1,表示该行有任务进入就绪态;例如,假设prio>>3 得到 Y坐标 = 3,则OSMapTbl[ 3 ] = 00001000,与OSRdyGrp做位或,将第四位置1;

第二条代码将prio与0x07=00000111 做位与,保留低三位,将其他位置0,得到X坐标,将X坐标填入OSMapTbl[ ],返回值与OSRdyTbl[ Y ]做位或,将OSRdyTbl[ Y ]对应位置1,表示该行的第X位有任务进入就绪态,注意第X位要从低端算起,也就是表格的右端开始算起;

例如:刚才已经算出Y坐标 = 3,假设prio & 0x07 = X坐标 = 4 ,OSMapTbl[ 4 ] = 00010000 ,将该返回值与OSMapTbl[ 3 ]做位或,将第五位置1,表示该位的任务进入就绪态;


b. 使脱离就绪态的方法:

 if((OSRdyTbl[Prio>>3] &=~OSMapTbl[Prio&0x07]) = = 0)
OSRdyGrp  &= ~OSMapTbl[Prio>>3];


c. 找出进入就绪态的优先级最高的任务


    这里作者Jean J.Labrosse 提供了一个查找表的,只要按照一定算法查找,很快就查出来了。


代码为:y = OSUnMapTbl[OSRdyGrp];


       x= OSUnMapTbl[OSRdyTbl[y]];


       prio = y<<3 +x;


这个表为:


这里 OSUnMapTbl[]同样是uC/OS-II为提高查找速度定义的一个数组,它共有256个元素,其定义如下:

INT8U const OSUnMapTbl[] = {

    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0

};


右边注释的值是代表OSRdyGrp的值。如果OSRdyGrp的值为01101000,即0x68,那么,我们可以根据右边的注释,要查第七行。是第七行的第八个(从0开始)。是3。再代入程序,算出x为2,将x和y 代入prio = y<<3 +x;,算出最高为26,再根据这个值,查出任务控制块优先级表,得到该任务的任务控制块。

<think>我们参考了多个关于STM32与ADS系列芯片(如ADS127L21、ADS1284、ADS1256)开发的引用资料,这些资料提到了DRDY引脚的功能和使用方法。现在用户的问题是:如何在STM32中同步ADS芯片的DRDY引脚高低电平变化。也就是说,如何准确地在DRDY引脚电平变化时进行响应,以确保数据的正确读取。根据引用:[^1]:DRDY引脚在数据转换完成后会被拉低,可以通过读取该引脚电平判断是否可以读取芯片数据。另外,也可以读取状态寄存器的DRDY位。[^2]:在连续读取模式下,当DRDY变低时示有新数据可用,此时可以在SCLK的控制下读取数据。操作必须在DRDY再次下降前的四个CLK周期内完成,否则数据可能损坏。[^3]:DRDY引脚指示采样率,其脉冲周期与采样率成反比。因此,同步DRDY引脚的方法主要有以下几种:1.**轮询方式(Polling)**:通过不断检测DRDY引脚的电平状态(通常为下降沿示数据就绪)来判断是否可以读取数据。这种方法简单,但占用CPU资源。2.**外部中断方式(ExternalInterrupt)**:将DRDY引脚连接到STM32的外部中断引脚,配置为下降沿触发(因为通常DRDY在数据就绪时会从高变低)。这样,当DRDY下降沿到来时,CPU会进入中断服务程序(ISR),在ISR中读取数据。这种方式响应及时,效率高,但需要注意中断处理时间要尽量短。3.**定时器输入捕获方式(TimerInputCapture)**:如果同时需要记录DRDY变化的时间戳,可以将DRDY连接到定时器的输入捕获通道。这样,在DRDY变化时(通常也是下降沿)触发输入捕获事件,记录下定时器的计数值(时间戳),同时可以触发中断读取数据。这种方法适用于需要精确计时或同步的应用。4.**SPI通信与DMA结合**(在支持的情况下):有些ADS芯片支持连续读取模式(如引用[2]中的RDATAC模式),此时当DRDY变低时,数据会在SCLK的控制下输出。可以使用STM32的SPI接口的DMA功能,结合DRDY的中断信号来触发DMA传输,实现高效率和低延迟的数据传输。然而,值得注意的是,在引用[2]中提到:整个数据转移操作必须在DRDY再次下降之前的四个CLK周期内完成,否则数据可能会损坏。因此,无论采用哪种方式,都需要确保在下一个数据就绪信号到来之前完成当前数据的读取。以下分别给出两种常用方式的实现方法:**方式1:轮询方式**```c//假设DRDY引脚连接在STM32的GPIO_PIN_0上,GPIOA端口while(1){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET){//检测到低电平//读取数据//通过SPI或其他接口读取ADC数据}//可以加入适当延时,但延时不能太长,以免错过DRDY信号}```**方式2:外部中断方式**步骤1:配置DRDY引脚为外部中断模式(下降沿触发)步骤2:编写中断服务函数(在中断中读取数据,注意处理速度要快)步骤3:启用中断示例代码(使用HAL库):```c//初始化代码中配置外部中断//假设DRDY连接到GPIO_PIN_0,GPIOAGPIO_InitTypeDefGPIO_InitStruct={0};GPIO_InitStruct.Pin=GPIO_PIN_0;GPIO_InitStruct.Mode=GPIO_MODE_IT_FALLING;//下降沿触发中断GPIO_InitStruct.Pull=GPIO_PULLUP;//根据ADS芯片的DRDY引脚特性,如果芯片内部没有上拉,则需要外部上拉或配置为内部上拉HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);//配置中断优先级并使能中断HAL_NVIC_SetPriority(EXTI0_IRQn,0,0);//设置优先级HAL_NVIC_EnableIRQ(EXTI0_IRQn);//使能中断//中断服务函数voidEXTI0_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);//HAL库的中断处理宏}//在HAL_GPIO_EXTI_Callback中处理回调函数(被HAL_GPIO_EXTI_IRQHandler调用)voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_Pin==GPIO_PIN_0){//这里读取数据//注意:在中断服务函数中尽量只做最简单的处理,如果读取数据耗时较长,可以考虑使用标志位,在主循环中读取//但由于时间要求严格(必须在下次DRDY下降沿前读完),建议立即读取Read_ADC_Data();//读取数据的函数}}```**注意**:在中断中读取数据时,如果SPI读取操作需要一定时间,需要确保整个读取时间不会错过下一次DRDY信号(根据采样率,如2kHz采样率时,两次DRDY脉冲间隔500us,所以必须在500us内完成读取)。如果读取时间过长,可能导致错过下一次数据,此时可以考虑降低采样率,或者使用DMA来加速读取。**方式3:DMA方式(连续模式)**引用[2]提到在连续读取模式下,只要DRDY变低就可以读取数据。我们可以使用外部中断触发DMA请求(如果MCU支持),或者结合外部中断来启动DMA传输。以使用外部中断启动DMA传输为例:1.在外部中断回调函数中启动一次SPI的DMA传输(传输长度为一个数据样本的长度,例如24位或32位)。2.在DMA传输完成中断中处理数据,并准备下一次传输(例如重置DMA传输配置)。但是,这种方法要求每一次DRDY中断就启动一次DMA传输,并且需要保证DMA传输能在下一次DRDY中断之前完成。另一种方式是使用STM32的SPI的硬件NSS信号管理(如果ADS芯片支持),但是引用[3]中使用的ADS1256没有使用SYNC引脚,而是发送SYNC命令,所以可能不适用。因此,这里重点推荐外部中断方式。**总结**:-对于低采样率(如几百Hz)的情况,轮询或外部中断均可。-对于高采样率(如几kHz以上),由于数据读取时间窗口非常短,建议采用外部中断方式,并且尽量优化读取数据的代码(使用DMA传输可以大幅减少CPU负担,但需要确保DMA传输的时间满足要求)。-在使用外部中断时,注意中断服务程序的执行时间,避免丢失后续中断。最后,用户需要根据实际使用的ADS芯片型号以及STM32型号(支持的中断、DMA功能等)选择合适的同步方法。为了进一步帮助用户,下面给出相关问题的参考:§§相关问题§§1.如何通过STM32的SPI接口读取ADS芯片的数据?2.如何在STM32中使用DMA来传输ADC数据?3.在STM32中,如果DRDY中断频率很高(比如50kHz),如何处理才能避免丢失中断?4.如何配置STM32的外部中断(下降沿触发)?</think> ### STM32 同步 ADS 芯片 DRDY 引脚电平变化的方法 为了在 STM32 中准确同步 ADS 芯片的 DRDY 引脚电平变化,可参考以下**三种核心方法**,结合芯片特性与 STM32 外设功能实现可靠同步: --- ### 1. **GPIO 中断模式(推荐)** **原理**: 将 DRDY 引脚连接到 STM32 的**外部中断引脚**(EXTI),配置为下降沿触发(因 DRDY 在数据就绪时拉低[^1][^2])。当电平变化时,触发中断立即读取数据。 **实现步骤**: ```c // STM32 HAL 库配置示例(以下降沿触发为例) void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == DRDY_Pin) { // 检测到 DRDY 下降沿 HAL_SPI_Receive(&hspi1, adc_data, 3, 100); // 读取 24 位数据 // 处理数据... } } // 初始化配置 GPIO_InitTypeDef GPIO_Init = {0}; GPIO_Init.Pin = DRDY_Pin; GPIO_Init.Mode = GPIO_MODE_IT_FALLING; // 下降沿中断 GPIO_Init.Pull = GPIO_PULLUP; // 使能上拉(确保高电平稳定) HAL_GPIO_Init(DRDY_GPIO_Port, &GPIO_Init); HAL_NVIC_EnableIRQ(EXTIx_IRQn); // 使能对应中断线 ``` **优势**: - **实时性强**:响应延迟在微秒级,满足高速采样(如 ADS1256 的 2kHz 模式[^3])。 - **资源占用低**:CPU 仅在 DRDY 变化时工作,适合低功耗场景。 **注意**:中断服务函数应尽量精简,避免长时间阻塞。 --- ### 2. **轮询检测模式** **原理**: 在 STM32 主循环中**周期检测 DRDY 电平**,发现低电平立即读取数据。 **实现步骤**: ```c while (1) { if (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) == GPIO_PIN_RESET) { HAL_SPI_Receive(&hspi1, adc_data, 3, 100); // 读取数据 // 处理数据... } // 可加入短暂延时(如 osDelay(1))避免 CPU 过载 } ``` **适用场景**: - 低采样率应用(如 ≤100Hz),或 CPU 负载不高的系统。 - 无法分配中断资源的场景。 **缺点**:响应延迟较高,可能错过高速数据(如 ADS1284 要求 4 个 CLK 周期内完成读取[^2])。 --- ### 3. **定时器捕获 + DMA(高精度同步)** **原理**: - **输入捕获**:将 DRDY 连接到定时器输入捕获通道(如 TIMx_CHy),记录下降沿的精确时间戳[^3]。 - **DMA 传输**:触发 DMA 自动读取 SPI 数据,避免 CPU 干预。 **应用场景**: - 需要同步多路传感器(如振动分析)。 - 采样率 >10kHz 的高速系统(如 ADS127L21[^1])。 **配置要点**: ```c // 伪代码流程 1. 配置 TIMx 为输入捕获模式(下降沿触发) 2. 使能捕获中断 → 记录时间戳 3. 配置 SPI+DMA:捕获中断中启动 DMA 传输 4. DMA 完成中断中处理数据 ``` --- ### **关键设计注意事项** 1. **电平稳定性**: - 若 ADS 芯片 DRDY 脉冲宽度过窄(如 ADS1256 的 500μs[^3]),建议在 STM32 端启用**施密特触发器输入**(通过 GPIO 寄存器配置)。 - 添加硬件滤波:DRDY 信号线上并联 100pF 电容,削弱噪声干扰。 2. **时序约束**: - ADS1284 等芯片要求 **SCLK 下降沿后 DRDY 拉高**,需严格满足 SPI 时钟相位配置[^2]: ```c hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // SCLK 空闲低电平 hspi1.Init.CLKPase = SPI_PHASE_2EDGE; // 第 2 边沿采样数据 ``` 3. **故障处理**: - 超时检测:若 DRDY 持续低电平 >2 倍采样周期,触发芯片复位(通过 RESET 引脚[^3])。 - 状态寄存器校验:读取 STATUS1 的 DRDY 位[^1],验证数据有效性。 --- ### **典型问题优化案例** **问题**:ADS1256 在 2kHz 采样时数据丢失。 **根因**:轮询检测延迟 >500μs。 **解决**:切换到外部中断模式 + DMA,实现全吞吐量数据采集。 --- ### 相关问题 1. 如何优化 STM32 中断响应延迟以确保高速 ADC 数据不丢失? 2. ADS1284 连续模式下 SPI 时序如何配置? 3. DRDY 信号受干扰时有哪些软件/硬件抗噪手段? 4. 如何通过 STM32 定时器实现多路 ADC 精确同步采样? [^1]: STM32开发ADS127L21芯片 [^2]: STM32 ADS1284的开发学习 [^3]: 基于STM32HAL库ADS1256调试笔记
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值