树莓派PICO使用INA226测量数据,及获取数据的方式(使用alter和中断)

之前使用INA226的文章主要是通过PICO反复的轮询,获取INA226的采集数据,但是INA226采集数据的间隔时间其实是比较长的,理想的情况是INA226完成采集之后,通过中断通知PICO去获取数据。INA226也的确支持这个方式。

翻看INA226的datasheet,可以发现相关的寄存器是06H和07H,06H主要是用于设定alter引脚的功能。06H的位功能如下:

    struct
    {
        bool alter_latch_enable           : 1;      // 设置Alter的锁定模式,0不锁存(缺省,故障恢复后自动清除),1锁存(不会自动清除,直到读取 REGISTER_MASK_ENABLE 寄存器之后);
        bool alter_polarity_bit           : 1;      // 设置Alter Pin的极性,0缺省(触发时拉低电平),1反转;
        bool math_overflow_flag           : 1;      // 计算溢出,该标记被设置为1时,预示着电流和功率数据可能无效;(读取)
        bool conversion_ready_flag        : 1;      // 当Alter Pin触发时,该标志标记来源是采样完成;(读取)
        bool alter_function_flag          : 1;      // 当Alter Pin触发时,该标志标记来源是警告;(读取)
        bool reserved_1                   : 1;
        bool reserved_2                   : 1;
        bool reserved_3                   : 1;
        bool reserved_4                   : 1;
        bool reserved_5                   : 1;
        bool conversion_ready             : 1;      // 触发Alter Pin,当采样完成
        bool power_over_limit             : 1;      // 触发Alter Pin,当功率超过阈值
        bool bus_voltage_under_voltage    : 1;      // 触发Alter Pin,当总线电压低于阈值
        bool bus_voltage_over_voltage     : 1;      // 触发Alter Pin,当总线电压超过阈值
        bool shunt_voltage_under_voltage  : 1;      // 触发Alter Pin,当分流器电压低于阈值
        bool shunt_voltage_over_voltage   : 1;      // 触发Alter Pin,当分流器电压超过阈值

    } mask;

 这个是从0位到15位的定义,PICO使用的是小端模式,因此定义是从低端到高端的位定义。

另外,07H寄存器内,放置的是警报的限制数据。

The Alert Limit Register contains the value used to compare to the register selected in the Mask/Enable Register to determine if a limit has been exceeded.

07H寄存器内放的数据是整形,因此,07H内放的数据是采样转换成浮点数之前的数据。我们在设定07H的时候,必须从浮点数转换成整数。方法如下

// Shunt voltage
// lsb 2.5uV, 0.000,0025
int16_t value = (int16_t)( upper / 0.0000025 );

// Bus Voltage
// lsb 1.25mV, 0.00125
int16_t value = (int16_t)( upper / 0.00125 );

// Power
// lsb 25mW, 0.025
uint16_t value = (uint16_t)( upper / 0.025 );


关于06H的设定上,必须有一个说明,就是报警位11-15位,不能多重设定,因为07H只能保存一个数据,因此,只能在5个警报方式内五选一,如果要改变设定,就必须把之前的设定位清除掉。

另外,当Alter pin被触发的时候,06H内也存储了触发的原因,包括三种:采样完成(设定了采样通知),警报(设定了警报),数据异常。我们可以通过这几个标志来确定Alter pin被触发的理由。Pico在中断之后,必须读取06H,获取到引发中断的原因,并根据条件继续做处理。

06H有一个比较重要的位,01位,是否反转Alter Pin的极性。Alter Pin是开漏模式,正常情况,PICO看来Alter Pin是高位,当触发警报的时候,Alter Pin会拉底Pico GPIO的电压并导通。所以,如果有特殊要求,可以改变这个值达到目的。

硬件的设计如下:

GPIO1,2通过上拉电阻接到INA226的数据端口,GPIO则通过电阻+LED接到Alter Pin。 这样,当Alter 开启的时候,LED就会发光提示。

硬件准备好,我们接下来就开始说一下如何实现PICO的中断部分。

GPIO的中断实现非常的简单,代码如下:

gpio_set_function( gpio, GPIO_FUNC_SIO );
gpio_pull_up( gpio );
gpio_set_irq_enabled_with_callback( gpio, event, true, & PicoInterruptEntry );

三条语句就能开启中断。而event则监控下拉事件,因为INA226的Alter Pin是开漏,因此,必须先把GPIO拉高,稍微麻烦点就是要设定一个回调函数入口,但是有一点必须提醒的是,中断仅仅是中断,仅仅用于处理数据接收,不能用来做非常复杂的事情,这种情况非常类似于多线程环境下的编程,因此,数据必须有临界区的保护,在中断程序处理了数据采集之后,退出临界区,并通过信号量通知主循环来处理数据。也就是说,所有重度操作,都必须放在主循环之内,而不是让中断来处理。中断结束的越快越好。

上面介绍了大致的框架,接下来,我简单的把关键的代码贴上来:

void CPicoFilamental::Entry(uint32_t events) 
{
    bool awaken(false);
    if( GPIO_IRQ_EDGE_FALL & events || GPIO_IRQ_LEVEL_LOW & events )
    {
        section.Enter();

        counter ++;
        bool alter_function(false), conversion_ready(false), math_overflow(false);
        if( sampler.GetFlags(alter_function, conversion_ready, math_overflow) )
        {
            //Serial.printf( "flags...%d, %d, %d\n", alter_function, conversion_ready, math_overflow );
        }

        if( GPIO_IRQ_EDGE_FALL & events )
        {
            if( conversion_ready && !math_overflow )
            {
                f_current = sampler.GetCurrent(); 
                awaken = true;
                //Serial.printf( "current: %f\n", f_current );
            }
        }
        else 
        if( GPIO_IRQ_LEVEL_LOW & events )
        {
            if( alter_function )
            {
                //Serial.printf( "alter error\n" );
                awaken = true;
            }
        }

        section.Leave();

        if( awaken ) {
            signal.Awake();
        }
    }
}

上面这一段是中断的处理程序,非常的简单,就是采集数据,根据结果简单的处理,然后通知主循环来干活。

bool CPicoFilamental::Run()
{
    double voltage_percent = 0.0;
    for( ;; )
    {
        section.Enter();

        double diff = abs( f_current - e_current );
        diff = diff / e_current;

        // 误差 1% 以内不执行任何操作
        if( diff > 0.01 )
        {
          double result = pid.StepCalculate( e_current, f_current ) * 0.0033;
          voltage_percent += result;
          voltage_percent = min( voltage_percent, 0.08 );
          voltage_percent = max( voltage_percent, 0.0 );

          pwm_1.Adjust( voltage_percent );
          pwm_2.Adjust( voltage_percent );

          Serial.printf( "feedback: %f, percent: %f, i:%f\n", f_current, voltage_percent, pid.integral );
        }

        // Convert to percent
        section.Leave();

        signal.Await( 2000 );
        signal.Reset();
    }
    return true;
}

上面这一段就是主循环的处理,大概就是通过电流值,用PID算法计算下一步的PWM占空比。然后输出PWM。重点是信号量的使用。演示了中断如何通过信号量来控制主循环。

#include "Signal.h"



CSignal::CSignal()
{
    sem_init( & signal, 0, 1 );
}

CSignal::~CSignal()
{
}

bool CSignal::Reset()
{
    sem_reset( & signal, 0 );
    return true;
}

bool CSignal::Awake()
{
    sem_reset( & signal, 1 );
    return true;
}

bool CSignal::Await(uint32_t milisecond)
{
    return sem_acquire_timeout_ms( & signal, milisecond ); 
}

上面信号量的代码其实也非常的简单。也是对PICO SDK的一个简单封装。

最后,必须要提一句,使用中断的方式,最好是采用触发模式,而不是使用连续模式。我在调试中发现,TI的芯片,如果采用连续模式,很有可能一段时间之后,读出来的数据是错的,Shunt Voltage的值全是FFFE之类的补码,我猜测是芯片本身的一些设计缺陷。采用触发模式,仅需要在所有的读取完成之后,重新触发一次采集即可(就是重新写入00H寄存器)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值