概述

该代码实现了一个简单的 ADC 数据采集功能,通过硬件寄存器操作控制 LTC2308 ADC 芯片,采集其指定通道的模拟信号并打印转换结果。

代码框架

如下:

 main.c 先是读取DE10-Standard开发板的开关值,将这个值作为通道选择信号输入给LTC2308控制器(adc_ltc2308_fifo模块)。然后main.c 给出一个10次的测量次数并将这个数据写入到LTC2308控制器的寄存器当中。接着main.c 给出了一个触发一轮测量的信号(measure_fifo_start)给LTC2308控制器, 开始一轮的测量。(adc_ltc2308_fifo模块调用adc_ltc2308模块 完成10次测量,并把每次测量的数据缓存到adc_data_fifo模块。main.c 等待10次测量完成以后,开始读取LTC2308控制器的FIFO中的数据。 一次读一个数据,10次读完延迟200ms,又开始新的一轮测量。 

NIOS II 处理器的硬件抽象层(HAL)

HAL提供了一些宏定义来访问外设的寄存器,关于HAL的具体描述请参考文章:

Nios II 处理器的硬件抽象层(HAL)

静态地址对齐

本案例的Nios 程序使用静态地址对齐(每个寄存器在Avalon总线上占4个字节的地址),在软件编程时

可以使用IOWR(基地址,寄存器编号(n),数据)对自定义IP的第n个寄存器进行写入操作;
可以使用IORD(基地址,寄存器编号(n)) 对自定义IP的第n个寄存器进行读出操作。

如果使用动态地址对齐,(每个寄存器在Avalon总线上占 数据位宽/8个字节的地址)
在软件编程时,使用IOWR_32DIRECT(数据位宽为32位)、IOWR_16DIRECT(数据位宽为16位)、IOWR_8DIRECT(数据位宽为8位)进行写操作
在软件编程时,使用IORD_32DIRECT(数据位宽为32位)、IORD_16DIRECT(数据位宽为16位)、IOWR_RDIRECT(数据位宽为8位)进行写操作

IOWR_8DIRECT(基地址、地址偏移量、数据),地址偏移量 = 寄存器编号*1
IOWR_16DIRECT(基地址、地址偏移量、数据),地址偏移量 = 寄存器编号*2
IOWR_32DIRECT(基地址、地址偏移量、数据),地址偏移量 = 寄存器编号*4

IORD_8DIRECT(基地址、地址偏移量),地址偏移量 = 寄存器编号*1
IORD_16DIRECT(基地址、地址偏移量),地址偏移量 = 寄存器编号*2
IORD_32DIRECT(基地址、地址偏移量),地址偏移量 = 寄存器编号*4

头文件引入

#include <stdio.h>    // 标准输入输出库(用于printf)
#include <io.h>       // 硬件寄存器操作库(IORD/IOWR)
#include <unistd.h>   // 提供usleep函数(微秒级延时)
#include "system.h"   // 自定义系统头文件(定义硬件地址)

在底层硬件里面,adc_ltc2308模块和sw模块的地址定义如下:

在软件BSP生成的时候会根据DE10_Standard_QSYS.sopcinfo文件生成system.h文件,其中就包含硬件寄存器的基地址定义:

 

代码详解

1. 主函数

void main(void){
    int ch = 0;
    const int nReadNum = 10; // max 1024
    int i, Value, nIndex=0;

变量说明:
ch:当前选择的 ADC 通道(0-7)。
nReadNum:每次采样的次数。
Value:存储每次 ADC 转换的结果。
nIndex:循环次数索引,用于打印调试信息。

2. 主循环

    while(1){

        ......

        printf("======================= %d, ch=%d\r\n", nIndex++, ch);

        ......

    } // while

程序将无限循环,每循环一次,nIndex自增1,这个变量是标识当前循环了多少次。

3. 读取开关值

        ch = IORD(SW_BASE, 0x00) & 0x07;//从开关寄存器读取输入值

& 0x07操作是取低3位,确保通道号在 0-7 范围内。寄存器的编号0x00、0x01的定义可以参考adc_ltc2308_fifo.v模块的代码。

4. 配置采样次数

        // set measure number for ADC convert
        IOWR(ADC_LTC2308_BASE, 0x01, nReadNum);

向 ADC 控制寄存器写入采样次数。

5. 启动ADC转换

        // start measure
        IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
        IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x01);
        IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
   usleep(1);

第一次写入:清除启动位。

第二次写入:设置启动位,开始转换。

第三次写入:清除启动位,准备下一次转换。

usleep(1):短暂延时,确保信号稳定。

6. 等待转换完成

        // wait measure done
        while ((IORD(ADC_LTC2308_BASE,0x00) & 0x01) == 0x00);

IORD(ADC_LTC2308_BASE, 0x00)是读取 ADC 状态寄存器,寄存器的最低位是10次采集完成标志位。

& 0x01:检查最低位是否为1。
== 0x00:结果为0则一直等待。

7. 读取并打印ADC数据

        // read adc value
        for(i=0;i<nReadNum;i++){
            Value = IORD(ADC_LTC2308_BASE, 0x01);
            printf("CH%d=%.3fV (0x%04x)\r\n", ch, (float)Value/1000.0, Value);
        }

IORD(ADC_LTC2308_BASE, 0x01):读取 ADC 数据寄存器。
(float)Value / 1000.0:将 ADC 原始值转换为电压值。

LTC2308的单极性状态下可以输入0~4.096V的范围,4.096v*value/212=value/1000。

 结果

 最终程序运行起来的打印结果如下:

 

往期推荐阅读: