hi3518e ADC 连续采样并获取电压值

对于hi3518e 主控芯片内部ADC采样的描述有位牛人描述的很到位。
https://blog.csdn.net/u013738338/article/details/78602986
那个大哥从datasheet出发深入浅出地将hi3518e ADC采样过程描述地很好。

接下来我要用改进过的hisi ADC源码来实现电源电压检测,这份代码读者直接获取、编译就可用。

首先介绍程序的架构。
1.hi_adc.ko提供ADC初始化、开始采样、结束采样、更改采样速度与周期、单次采样、连续采样、挂采样结束中断处理函数等相关函数。
2.power.ko中以定时器软中断的方式从hi_adc.ko提供的接口中定时获取 ADC采样值。
我们先看hi_adc.ko的源码(改进过的默认连续采样)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/kthread.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <mach/irqs.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>

#include "hi_adc.h"

/***************************Define Macro*************************/
#define SUPPORT_CHANNEL    0x4

#define SAR_ADC_BASE       0x200B0000
#define PERI_CRG32_BASE    0x2003007C

#define ADC_CTRL           0x00  //寄存器配置
#define ADC_GLITCH_SAMPEL  0x04  //滤波毛刺配置
#define ADC_TIME_SCAN      0x08
#define ADC_DATA_MASK      0x0c
#define ADC_INT_MASK       0x10  //int enable, 0:disable, 1:enable
#define ADC_INT_STATUS     0x14  //int state
#define ADC_INT_CLR        0x18  //int clr
#define ADC_START          0x1c  //start auto scan
#define ADC_STOP           0x20  //stop

#define ADC_IRQ_ENABLE     0x01
#define ADC_IRQ_DISABLE    0x00
#define ADC_CLK_ENABLE     (1<<2)  //0为使能
#define ADC_POWER_ENABLE   0x00
#define TIME_OUT           HZ * 5    // 队列等待时间 

//ADC_CTRL
#define ADC_ACTIVE_BIT(x)     (x << 24)    //precision, 11111100(6 bits),11111000(5 bits)...
#define DATA_DELTA(x)         (x << 20)    //误差范围,连续扫描模式配置值>2,手册没有说明
#define DEGLITCH_BYPASS       (1 << 17)    //滤毛刺, 0:enable, 1:bypass
#define ADC_RESET             (1 << 15)    //reset, 0:quit, 1:enter reset
#define POWER_DOWN_MODE       (1 << 14)    //power_down, 0:disable, 1:enable
#define MODEL_SEL             (1 << 13)    //model, 0:single scan, 1:continuous scan
#define CHANNEL_SEL(x)        (1 << (x+8)) //channel, 0:A, 1:B, 2:C, 3:D
#define ADC_ZERO_MASK         0xffffff00
//ADC_GLITCH_SAMPEL
#define GLITCH_SAMPEL_VAL  5
//ADC_TIME_SCAN
#define TIME_SCAN_VAL      247525  //500ms default, 2.02us per
#define THRESHOLD     253
//ADC_DATA_MASK
//#define get_adc_data(x)       ((SAR_ADC_BASE+ADC_DATA_MASK) >> (x << 3) & 0xFF)
//ADC_INT_STATUS
//#define ADC_AUTO_BUSY         ((SAR_ADC_BASE+ADC_INT_STATUS) >> 4 & 0x01)
//#define ADC_INT_FLAG(x)       ((SAR_ADC_BASE+ADC_INT_STATUS) >> x & 0x01) //中断标志, 0:A, 1:B, 2:C, 3:D
//ADC_INT_CLR
//#define ADC_INT_CLR(x)        ((SAR_ADC_BASE+ADC_INT_CLR) | 0x01 << x) //清除中断标志, 0:A, 1:B, 2:C, 3:D
//#define DEBUG_ADC
#define DELAY_MS 100
/***********************Define Globle Variate********************/
struct his_adc_driver{
    struct proc_dir_entry  *adc_file;
    void __iomem           *adc_reg_base;
    int                    flag;
    wait_queue_head_t      irq_wait;
};

static char *adc_proc_name = "pay_adc";
static struct timer_list g_AdcTimer;
static int g_voltage = 0;
static struct his_adc_driver his_adc;
/****************Driver Information Indicate********************/
MODULE_AUTHOR("jinfa.");
MODULE_DESCRIPTION("hi_adc driver");
MODULE_LICENSE("GPL");

void adc_set_freq_ms(int ms)   //ms per
{
    unsigned int time_val = TIME_SCAN_VAL * ms / 500;
    writel(time_val, his_adc.adc_reg_base + ADC_TIME_SCAN);
}
EXPORT_SYMBOL(adc_set_freq_ms);


static void AdcTimerFunc(unsigned long Data)
{
    g_voltage = readl(his_adc.adc_reg_base + ADC_DATA_MASK);
    if (g_voltage > THRESHOLD)
        return; 
}

void adc_start(void)
{
    // enable the interrupt
    writel(ADC_IRQ_ENABLE, his_adc.adc_reg_base + ADC_INT_MASK);
    // start convert
    writel(0x01, his_adc.adc_reg_base + ADC_START);
}
EXPORT_SYMBOL(adc_start);

void adc_stop(void)
{
    // disable the interrupt
    writel(ADC_IRQ_DISABLE, his_adc.adc_reg_base + ADC_INT_MASK);
    //stop convert
    writel(0x01, his_adc.adc_reg_base + ADC_STOP);
}
EXPORT_SYMBOL(adc_stop);

int adc_read_channel(int channel)
{
    unsigned int adc_ctrl_reg = 0, adc_data;

    // enable the interrupt
    writel(ADC_IRQ_ENABLE, his_adc.adc_reg_base + ADC_INT_MASK);

    // choose the channel
    adc_ctrl_reg = readl(his_adc.adc_reg_base + ADC_CTRL);
    adc_ctrl_reg |= CHANNEL_SEL(channel);
    printk("adc_ctrl reg is %u\n", adc_ctrl_reg);
    writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);

    // start convert
    writel(0x01, his_adc.adc_reg_base + ADC_START);

    wait_event_timeout(his_adc.irq_wait, his_adc.flag, TIME_OUT);
    if (0 == his_adc.flag)
        return -EFAULT;
    his_adc.flag = 0;

    // disable the interrupt
    writel(ADC_IRQ_DISABLE, his_adc.adc_reg_base + ADC_INT_MASK);

    // get the result
    adc_data = readl(his_adc.adc_reg_base + ADC_DATA_MASK);
    printk("adc data is %u\n", adc_data);
    return (adc_data >> (channel<<3) & 0xff);
}
EXPORT_SYMBOL(adc_read_channel);

static int hisi_adc_proc_read(char *page, char **start,
    off_t off, int count, int *eof, void *data)
{
    int len = 0;
    int i;
    for (i = 0; i < SUPPORT_CHANNEL; i++)
    {
        page[i] = adc_read_channel(i);
        len += sizeof(page);
        udelay(1);
    }
    *eof = 1;
    return len;
}
#if pay_power
int set_adc_channel(int channel)
{
     unsigned int adc_ctrl_reg = 0, adc_data;

    // enable the interrupt
    writel(ADC_IRQ_ENABLE, his_adc.adc_reg_base + ADC_INT_MASK);

    // choose the channel
    adc_ctrl_reg = readl(his_adc.adc_reg_base + ADC_CTRL);
    adc_ctrl_reg |= CHANNEL_SEL(channel);
    printk("adc_ctrl reg is %u\n", adc_ctrl_reg);
    writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);

    // start convert
    writel(0x01, his_adc.adc_reg_base + ADC_START);
}

int get_adc_channel(int channel)
{
    // get the result
    g_voltage = readl(his_adc.adc_reg_base + ADC_DATA_MASK);
    return (g_voltage >> (channel<<3) & 0xff);
}

void init_power_adc()
{
    adc_set_freq_ms(2);
    set_adc_channel(2);
}
EXPORT_SYMBOL(init_power_adc);

int get_power_vol()
{
    g_voltage = get_adc_channel(2); //直接获取通道二的采样结果
    return g_voltage;
}
EXPORT_SYMBOL(get_power_vol);
#endif

static irqreturn_t hi_adc_interrupt(int irq, void *id)
{
    // clear the interrupt
    writel(0x1, his_adc.adc_reg_base + ADC_INT_CLR);    
    // disable the interrupt
    writel(ADC_IRQ_DISABLE, his_adc.adc_reg_base + ADC_INT_MASK);
    return 0;
}

/*2018.5.9
 *log:分析该init_adc发现这里做了adc的初始化,有效通道为0
 *将设置有效通道0代码除去.因为设置有效通道放在set_adc_channel做
*/
int hisi_init_adc(void)
{
    int retval = 0;
    unsigned int adc_ctrl_reg=0,clk_reg;

    init_timer(&g_AdcTimer);
    g_AdcTimer.expires = jiffies + msecs_to_jiffies(DELAY_MS);
    g_AdcTimer.function = AdcTimerFunc;

    his_adc.adc_file = create_proc_read_entry(adc_proc_name, 0, NULL,
            hisi_adc_proc_read, NULL);
    if (his_adc.adc_file == NULL) {
        pr_warning("%s: %s fail!\n", __func__, adc_proc_name);
        return -ENOMEM;
    }

    retval = request_irq(INTNR_ADC, hi_adc_interrupt, 0, "HI_ADC", NULL);
    if(0 != retval){
        pr_warning("hi3518 ADC: failed to register IRQ(%d)\n", retval);
        goto ADC_INIT_FAIL1;
    }

    his_adc.adc_reg_base = ioremap_nocache((unsigned long)SAR_ADC_BASE, 0x20);
    if (NULL == his_adc.adc_reg_base){
        pr_warning("function %s line %u failed\n", __FUNCTION__, __LINE__);
        retval = -EFAULT;
        goto ADC_INIT_FAIL2;
    }

    adc_ctrl_reg = readl(his_adc.adc_reg_base + ADC_CTRL);
    //1.adc reset
    adc_ctrl_reg |= ADC_RESET;
    writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
    adc_ctrl_reg &= ~ADC_RESET;
    writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
    //2.adc config 8-bits
    //adc_ctrl_reg |= ADC_ACTIVE_BIT(0xff) & (~MODEL_SEL);

    //config contunious sample
    adc_ctrl_reg |= ADC_ACTIVE_BIT(0xff) | MODEL_SEL |
                   POWER_DOWN_MODE | DATA_DELTA(5);
    //adc_ctrl_reg = adc_ctrl_reg&ADC_ZERO_MASK + 127;
    writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
    //glitch_sample
    writel(GLITCH_SAMPEL_VAL, his_adc.adc_reg_base + ADC_GLITCH_SAMPEL);
    //time_scan
    writel(TIME_SCAN_VAL, his_adc.adc_reg_base + ADC_TIME_SCAN);
    //3.CLK and cancell soft reset
    clk_reg = readl(IO_ADDRESS(PERI_CRG32_BASE));
    clk_reg &= ~ADC_CLK_ENABLE;
    writel(clk_reg, IO_ADDRESS(PERI_CRG32_BASE));

    his_adc.flag = 0;
    init_waitqueue_head(&his_adc.irq_wait);
    return 0;

ADC_INIT_FAIL2:
    free_irq(INTNR_ADC, NULL);
ADC_INIT_FAIL1:
    remove_proc_entry(adc_proc_name, NULL);
    return retval;
}

void hisi_exit_adc(void)
{
    del_timer(&g_AdcTimer);
    free_irq(INTNR_ADC, NULL);
    iounmap(his_adc.adc_reg_base);
    remove_proc_entry(adc_proc_name, NULL);
}
module_init(hisi_init_adc);
module_exit(hisi_exit_adc);

hisi官方adc源码提供adc_start、adc_stop、adc_read_channel、adc_set_freq_ms设置采样周期并且hi_adc采样结束后会触发中断hi_adc_interrupt。
不过,为了匹配我的power.ko我新增了一些函数:set_adc_channel、get_adc_channel、init_power_adc这些接口做EXPORT_SYMBOL给power.ko用的。
那么,我们现在看看我写的power驱动的部分源码、测试用例。

/*
 *power驱动开定时器软中断
 *定时获取电压采样值也定时唤醒应用下来的执行序列。
*/
static void Get_PowerTimerFunc(unsigned long Data)
{
    static int fre=0;
    g_electric = get_power_vol(); //调用hi_adc.ko的接口函数
    mod_timer(&g_Power_Timer, jiffies+HZ*3);
    g_PowerOccurred=1;
    wake_up_interruptible(&power_waitq); //唤醒应用下来的执行序列
}

static int power_read(struct file *file, char *user, size_t size, loff_t *oppos)
{
    int ret=0;
    ret = wait_event_interruptible(power_waitq, (0 != g_PowerOccurred)); //使应用下来的执行序列阻塞
    if (0 != ret)
    {
        printk("Exit WaitComplete function by signal :%d\n", ret);
        return -EBADRQC;
    }
    copy_to_user(user, (const void *)&g_electric, 4);
    return 0;
}

测试用例

read(Power_IndLedFD, &electric, 4);
sleep(1);
printf("electric:%d\n", electric);

代码小解析:估计读者对于get_adc_channel这直接返回存放adc采样值寄存器中的值感到疑惑。。为什么不等中断来了才允许读?这么个读值方法不会导致数据的不新鲜吗?
答案:的确是这样。但是对结果没影响。因为,adc采样时ms级别的而电源电压的变化很小。。所以即使调get_power_vol足够频繁,捞上来的电源电压值不新鲜还是没问题的!


猜你喜欢

转载自blog.csdn.net/huang_165/article/details/80453250