概述
文章目录
- 1. ADC
- 1.1 ADC相关寄存器
- 1.1.1 ADC 控制寄存器1(ADC_CR1)
- 1.1.2 ADC 控制寄存器2(ADC_CR2)
- 1.1.3 ADC 采样时间寄存器(ADC_SMPR1 和 ADC_SMPR2)
- 1.1.4 序列寄存器(ADC_SQR1~3)
- 1.1.5 ADC 规则数据寄存器(ADC_DR)
- 1.1.6 ADC注入通道数据偏移寄存器(ADC_JOFR)
- 1.1.7 ADC 状态寄存器(ADC_SR)
- 1.2 ADC初始化一般步骤
- 1.3 核心代码
- 1.3.1 关于ADC电压转换与线性修正算法
- 1.4 ADC功能框图
- 1.4.1 电压输入范围
- 1.4.2 模拟信号输入通道
- 1.4.3 信号转换顺序
- 1.4.3.1 规则通道转换顺序
- 1.4.3.2 注入通道转换顺序
- 1.4.4 触发源
- 1.4.5 转换时间
- 1.4.6 数据寄存器
- 1.4.7 中断
- 1.5 ADC芯片ADC0809应用
- 1.5.1 STM32与芯片连接电路
- 1.5.2 工作过程
- 1.5.3 时钟信号
- 1.5.3 核心代码
- 1.6 ADC芯片AD5592应用
- 1.7光强采集芯片BH1750FVI应用
- 1.7.1 核心代码
- 1.7.2 BH1750采集速度与MCU采集速度区别
- 1.8 ADC芯片 - AD7780应用
- 1.8.1 AD7780 小电阻与大电阻测量方法
- 1.9 解决ADC 分辨率不够导致测试进度不够的解决方案
- 2. 存储
- 2.1 EEPROM芯片 - AT24Cxx
- 2.1.1 写操作
- 2.1.2 读操作
- 2.1.3 核心代码
- 2.2 延长EEPROM寿命
- 2.3 关于存储器读写地址
- 3. 中断
- 3.1 STM32 的中断
- 3.1 NVIC 嵌套向量中断控制器
- 3.1.1 中断优先级分组(中断管理方法)
- 3.1.2 NVIC 参数结构体
- 3.2 外部中断/事件控制器 EXTI
- 3.3 中断服务函数
- 3.4 STM32 中断优先级寄存器配置及其参考代码
- 3.5 其他:
1. ADC
ADC(Analog-to-digital converters,模数转换器),STM32上的ADC可独立使用也可双ADC搭配使用以提高采样率,STM32F1系列的ADC是12 位逐次逼近型的,也即是12位精度,它有 18 个通道,可测量 16 个外部和 2 个内部信号源。每个通道的 A/D 转换可以单次、连续、扫描或间断模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
ADC的时钟频率由PCLK2分频产生,在 ADC时钟ADCCLK=14M、采样周期为 1.5 个 ADC 时钟下,STM32 的 ADC 最大的转换速率为 1Mhz,即转换时间为 1us,若ADCCLK超过14M,将导致转换结果的准确度下降。
- STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组,其中注入通道组相当于中断,在执行规则通道上的转换时,注入通道的转换可打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。STM32F1系列 其 ADC 的规则通道组最多包含 16 个转换,而注入通道组最多包含 4 个通道。它们的转换顺序见1.4.3节。
1.1 ADC相关寄存器
1.1.1 ADC 控制寄存器1(ADC_CR1)
Control register 1.
- SCAN(扫描):用于设置扫描模式,由软件设置和清除,如果设置为 1,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只在最后一个通道转换完毕后才会产生 EOC 或 JEOC 中断。
- AWDEN:用于使能温度传感器和 Vrefint。
- DUALMOD(双模式选择):用于设置ADC的操作模式。
1.1.2 ADC 控制寄存器2(ADC_CR2)
Control register 2.
- ADON(AD开):用于开关 AD 转换器。
- CONT(连续转换):用于设置是否进行连续转换,1为连续转换,0为单次转换。
- CAL 和 RSTCAL(计算与重计算):用于AD校准。
- ALIGN(对齐):用于设置数据对齐。 0为右对齐,1为左对齐。
- EXTSEL:用于选择启动规则转换组转换的外部事件。其中SWSTART表示软件触发。
1.1.3 ADC 采样时间寄存器(ADC_SMPR1 和 ADC_SMPR2)
用于设置通道 0~17 的采样时间,每个通道占用 3 个位。
对于采样时间,时间越长,准确度越高,但同时也降低了 ADC 的转换速率。ADC 的转换时间计算公式为:Tcovn=采样时间+12.5 个周期,其中:Tcovn 为总转换时间,采样时间是根据每个通道的 SMP 位的设置来决定。
如:当 ADCCLK=14Mhz 的时候,并设置 采样时间为1.5 个周期,根据上述公式有:Tcovn=1.5+12.5=14 个周期=1us.
1.1.4 序列寄存器(ADC_SQR1~3)
该寄存器总共有 3 个,但功能都一样,这里仅介绍ADC_SQR1.
- L[3:0](规则通道序列长度):用于存储规则序列的长度,这里只用了 1 个,所以设置这几个位的值为 0.
- ** SQ13~16**:存储了规则序列中第 13~16 个通道的编号(0 ~17)。
如:选择的是通道1,就需要在寄存器ADC_SQR3中的最低5为(即SQ1)中设置。
1.1.5 ADC 规则数据寄存器(ADC_DR)
规则序列中的 AD 转化结果都将被存在这个寄存器中,而注入通道的转换结果被保存在 ADC_JDRx 中。
注:该寄存器的数据可以通过 ADC_CR2 的 ALIGN 位设置左对齐还是右对齐。在读取数据的时候要注意。
1.1.6 ADC注入通道数据偏移寄存器(ADC_JOFR)
该寄存器共有4个,而注入通道本身就只有4个,所以注入通道转换的数据都有固定的存放位置,不会跟规则寄存器那样产生数据覆盖的问题。 ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。
1.1.7 ADC 状态寄存器(ADC_SR)
该寄存器保存了 ADC 转换时的各种状态。
- ** EOC(转换结束)**:通过判断该位来决定是否此次规则通道的 AD 转换已经完成,如果完成我们就从 ADC_DR 中读取转换结果,否则等待转换完成。
1.2 ADC初始化一般步骤
使用到的库函数引自
stm32f10x_adc.c
和stm32f10x_adc.h
文件中。
以STM32F103ZET中 ADC1 的通道 1 进行 AD 转换为例:
- 使用 ADC1 的通道 1 进行 AD 转换: 已知ADC 通道 1 在 PA1 上,所以先要使能 GPIOA 的时钟和 和 ADC1时钟,然后设置 PA1 为模拟输入。使能 GPIOA 和 ADC 时钟用
RCC_APB2PeriphClockCmd
函数,设置 PA1 的输入方式,使用GPIO_Init
函数即可。 - 复位 ADC1,同时设置 ADC1 分频因子:开启 ADC1 时钟之后,再复位 ADC1,将 ADC1 的全部寄存器重设为缺省值之后就可通过 RCC_CFGR 设置 ADC1 的分频因子。分频因子要确保 ADC1 的时钟(ADCCLK)不要超过 14Mhz。 这里设置分频因子为 6,时钟为 72/6=12MHz.
//设置分频因子为 6
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//复位 ADC1
ADC_DeInit(ADC1);
- 初始化 ADC1 参数,设置 ADC1 的工作模式以及规则序列的相关信息:ADC相关参数配置,在库函数的
ADC_Init
中完成。
//ADC_Init的定义
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
//ADC_Init的第二个入口参数通过结构体变量配置
typedef struct
{
uint32_t ADC_Mode; // ADC工作模式
FunctionalState ADC_ScanConvMode; //是否开启扫描
FunctionalState ADC_ContinuousConvMode; //是否开启连续转换
uint32_t ADC_ExternalTrigConv; //设置启动规则转换组转换的外部事件
uint32_t ADC_DataAlign; //设置 ADC 数据对齐方式
uint8_t ADC_NbrOfChannel; //设置规则序列的长度
}ADC_InitTypeDef;
- 使能 ADC 并校准:使能 AD 转换器,执行复位校准和 AD 校准。
//使能指定的 ADC
ADC_Cmd(ADC1, ENABLE);
//复位校准
ADC_ResetCalibration(ADC1);
//ADC校准
ADC_StartCalibration(ADC1);
//等待复位校准结束与等待AD校准结束
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
while(ADC_GetCalibrationStatus(ADC1)); //等待校 AD 准结束
- 读取 ADC 值:设置规则序列 1 里面的通道、采样顺序以及通道的采样周期,然后启动 ADC 转换,在转换结束后,读取 ADC 转换结果值。
//设置规则序列通道以及采样周期的函数定义
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,uint8_t Rank, uint8_t ADC_SampleTime);
//如:设置规则序列中的第 1 个转换,同时采样周期为 239.5
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
//使能指定的 ADC1 的软件转换启动功能
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//获取转换 ADC 转换数据
ADC_GetConversionValue(ADC1);
//等待转换结束
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
1.3 核心代码
void Adc_Init(void)
{
//定义结构体变量
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能 ADC1 通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置 ADC 分频因子 6
//72M/6=12,ADC 最大时间不能超过 14M
//PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.1
ADC_DeInit(ADC1); //复位 ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //不开启扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC 数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC 通道数
ADC_Init(ADC1, &ADC_InitStructure); //根据指定的参数初始化外设 ADCx
ADC_Cmd(ADC1, ENABLE); //使能指定的 ADC1
ADC_ResetCalibration(ADC1); //开启复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启 AD 校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
//获得 ADC 值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)
{
//设置指定 ADC 的规则组通道,设置它们的转化顺序和采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //通道 1
//规则采样顺序值为 1,采样时间为 239.5 周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的 ADC1 的软件转换功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次 ADC1 规则组的转换结果
}
// 用于多次获取 ADC 值,取平均,用来提高准确度
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
int main(void)
{
u16 adcx;
float temp;
delay_init(); //延时函数初始化
NVIC_Configuration(); //设置 NVIC 中断分组 2
uart_init(9600); //串口初始化波特率为 9600
Adc_Init(); //ADC 初始化
//显示提示信息
while(1)
{
adcx=Get_Adc_Average(ADC_CH1,10);
LCD_ShowxNum(156,130,adcx,4,16,0);//显示 ADC 的值
temp=(float)adcx*(3.3/4096); // 计算电压值的算法
adcx=temp;
LCD_ShowxNum(156,150,adcx,1,16,0);//显示电压值
temp-=adcx;
temp*=1000;
LCD_ShowxNum(172,150,temp,3,16,0X80);
delay_ms(250);
}
}
1.3.1 关于ADC电压转换与线性修正算法
- ADC转换算法:从ADC端口采集而来的模拟值通过ADC转换后得到的数据是一个12位的二进制数,电压转换的工作就是把这个二进制数代表的模拟量用数字表示出来。
如:测量的电压范围是0~3.3V,设ADC采集转换后得到的二进制数是
ADC_Value
,因为12位ADC在转换时将电压的范围大小(即3.3)分为4096(即2^12)份,所以转换后的二进制数ADC_Value
代表的实际电压Voltage
算法是:
ADC_Value/Voltage=4096/3300
即
Voltage=(3300*ADC_Value)/4096
- 电压转换的线性修正:原理是直线的斜截式方程
y=kx+b
,考虑硬件电路元器件的参数差异,需加入随时可改的线性修正值,以保证采集值ADC_Value
与实际电压Voltage
的转换保持在线性关系;
调试时一般只需读取两个不同的电压值,然后计算其直线关系,再设定斜率
k
和截点b
;读取的电压值数据越多,计算的修正值越准确;
- 参考代码:
u16 ADC_CAL_voltage(void)
{
u16 adcx;
float temp;
float ad_k,ad_b;
ad_k=IIC_Read_float(ad_k_Add);//从EEPROM中读取线性修正值
ad_b=IIC_Read_float(ad_b_Add);
adcx=Get_Adc_Average(ADC_Channel_6,20);//获取并计算10次ADC采样数据平均值
temp=(float)ad_k*adcx*(3.3/4096)*1000*8.1/3+ad_b;//求出mV,增加线性校准系数,其中1000为放大倍数(从V转换到mV),8.1/3为分压电阻修正值
adcx=temp;//得到电压值
// temp-=adcx;
// temp*=1000;
delay_Nms(250);
// sprintfU3("The ADCVol is %dmVrn@_@",adcx);
sprintfU3("%dmVrn@_@",adcx);
return adcx;
}
- 关于分压电阻修正值:如下图,设实际电压值为X,经分压后读取的电压值为Y,则从Y转换到X的算法是:
X/Y=8.1/3
即
X=Y*(8.1/3)
1.4 ADC功能框图
1.4.1 电压输入范围
ADC所能测量的电压范围就是VREF- ≤ VIN ≤ VREF+,若把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0~3.3V.
1.4.2 模拟信号输入通道
模拟信号通过通道输入到单片机中,单片机经过转换后,将模拟信号输出为数字信号。STM32F1系列中的ADC有着18个通道,其中外部的16个通道已经在框图中标出。
这16个通道对应着不同的GPIO端口,此外ADC1/2/3 还有内部通道: ADC1 的通道 16 连接到了芯片内部的温度传感器, Vrefint 连接到了通道 17。 ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。
ADC的全部通道如示:
1.4.3 信号转换顺序
1.4.3.1 规则通道转换顺序
规则通道中的转换顺序由三个序列寄存器控制,它们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序。
1.4.3.2 注入通道转换顺序
注入通道的转换也是通过 注入序列寄存器(JSQR寄存器来) 来控制,控制关系如下:
注:只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。当JL<4时,注入通道的转换顺序恰恰相反,也就是执行顺序为:JSQ4、JSQ3、JSQ2、JSQ1.
1.4.4 触发源
像通信协议一样,都要规定一个起始信号才能传输信息,ADC也需要一个触发信号来实行模/数转换。
- 直接通过配置 控制寄存器2(CR2) 的ADON位(使能AD转换器),写1时开始转换,写0时停止转换。
- 通过内部定时器或者外部IO触发AD转换,即可以利用内部时钟让ADC进行周期性的转换,也可以利用外部IO使ADC在需要时转换,具体的触发由控制寄存器CR2决定。
1.4.5 转换时间
DC的每一次信号转换都要一定的时间,转换时间由输入时钟和采样周期共同决定。
- 输入时钟:ADC在STM32中是挂载在APB2总线上的,所以ADC的时钟是由PCLK2(72MHz)经过分频得到的,分频因子由 RCC 时钟配置寄存器RCC_CFGR 的位 [15:14] ADCPRE[1:0]设置,可以是 2/4/6/8 分频,一般配置分频因子为8,即8分频得到ADC的输入时钟频率为9MHz。
- 采样周期:采样周期建立在输入时钟上,采样周期也即是使用多少个ADC时钟周期来对电压进行采样,采样周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置。每个通道可以配置不同的采样周期,但最小的采样周期是1.5个周期,即如果想最快时间采样就设置采样周期为1.5.
- 转换时间:转换时间=采样时间+12.5个周期,12.5个周期是固定的,一般设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。
1.4.6 数据寄存器
转换完成后的数据存放在数据寄存器中,且规则通道转换数据和注入通道转换数据是分开存放的。
- 规则数据寄存器:负责存放规则通道转换的数据,在32位寄存器ADC_DR中存放。当使用ADC独立模式(也就是只使用一个ADC,但可以使用多个通道)时,数据存放在低16位中,当使用ADC多模式时高16位存放ADC2的数据。需要注意的是ADC转换的精度是12位,而寄存器中有16个位来存放数据,所以要规定数据存放是左对齐还是右对齐。
当使用多个通道转换数据时,会产生多个转换数据,然而数据寄存器只有一个,多个数据存放在一个寄存器中会覆盖数据导致ADC转换错误,所以我们经常在一个通道转换完成之后就立刻将数据取出来,方便下一个数据存放。一般开启DMA模式将转换的数据,传输在一个数组中,程序对数组读操作就可以得到转换的结果。
DMA介绍
- 注入数据寄存器:详见1.1.7节。
1.4.7 中断
从框图中可知数据转换完成后可以产生三种中断,这些中断都在*ADC状态寄存器(ADC_SR)*配置:
- 规则通道转换完成中断(EOCIE):规则通道数据转换完成之后,产生一个中断,可在中断函数中读取规则数据寄存器的值。也是单通道时读取数据的一种方法。
- 注入通道转换完成中断(JEOCIE):注入通道数据转换完成之后,产生一个中断,并且也可在中断中读取注入数据寄存器的值,达到读取数据的作用。
- 模拟看门狗事件(AWDIE):当输入的模拟量(电压)不在阈值范围内就会产生看门狗事件,就是用来监视输入的模拟量是否正常。
1.5 ADC芯片ADC0809应用
STM32驱动ADC0809详解
ADC0809 datasheet
1.5.1 STM32与芯片连接电路
ADC0809引脚 | STM32引脚 | GPIO方向 |
---|---|---|
START | PA2 | 输出 |
EOC | PA3 | 输入 |
OE | PA4 | 输出 |
CLOCK | PA7 | 输出 |
ALE | PA6 | 输出 |
ADD A | PA5 | 输出 |
ADD B | PB10 | 输出 |
ADD C | PB11 | 输出 |
ADC0809_D0(最低位) | PA11 | 输入 |
ADC0809_D1 | PA12 | 输入 |
ADC0809_D2 | PC10 | 输入 |
ADC0809_D3 | PC11 | 输入 |
ADC0809_D4 | PC12 | 输入 |
ADC0809_D5 | PD2 | 输入 |
ADC0809_D6 | PB13 | 输入 |
ADC0809_D7(最高位) | PB12 | 输入 |
- 时序图:
1.5.2 工作过程
-
控制与ADD A~ADD C相连的引脚,选择一个模拟输入端;
-
CLOCK端输入一个时钟信号,这里通过STM32的PWM实现此时钟脉冲,脉冲频率100 KHz;
-
将ALE由低电平置为高电平,从而将ADD A-ADD C送进的通道锁存,经译码后被选中的通道的模拟量送给内部转换单元;
-
给START一个正脉冲。当上升沿时,所有内部寄存器清零。下降沿时,开始进行A/D转换;在转换期间,START保持低电平;
-
读取EOC引脚的状态,A/D转换期间,EOC输入低电平;A/D转换结束,EOC引脚输入高电平;
-
当A/D转换结束后,将OE设置为1,这时D0-D7的数据便可以读取了。
1.5.3 时钟信号
根据datasheet,芯片时钟信号允许的范围为:
这个脉冲信号可以采用定时器中断的方式来产生脉冲信号或使用PWM的方式来产生脉冲信号,下面采用PWM的方式,在STM32的引脚中选择了一个带有PWM功能的引脚PA7:TIM3_CH2.
1.5.3 核心代码
- AD转换:
因为ADC0809为8位的AD芯片,所以将8位数据中的每一位数据缓存至一个数组中,然后对这个数组中的值求和即为此次AD的采样值。
这里参考电压Vref(+)=+5V ,Vref(-)=0 ,所以8位数的最大值0xFF对应5V,0x00对应0,所以AD采样值和电压值的换算公式为:adc = (float)sum*5/256;
.
float get_adc0809()
{
int i=0;
u8 sum=0;
float adc=0;
int AD_DATA[8] = {0};
ADC0809_ALE=0;
ADC0809_START=0;
delay_us(10);
ADC0809_ALE=1;
ADC0809_START=1;
delay_us(10);
ADC0809_ALE=0;
ADC0809_START=0; //启动AD转换
while(0==ADC0809_EOC); //等待转换结束
ADC0809_OE=1;
AD_DATA[0]=ADC0809_D0*1 ;
AD_DATA[1]=ADC0809_D1*2 ;
AD_DATA[2]=ADC0809_D2*4 ;
AD_DATA[3]=ADC0809_D3*8 ;
AD_DATA[4]=ADC0809_D4*16 ;
AD_DATA[5]=ADC0809_D5*32 ;
AD_DATA[6]=ADC0809_D6*64 ;
AD_DATA[7]=ADC0809_D7*128 ;
ADC0809_OE=0;
for(i=0; i<8; i++)
{
sum += AD_DATA[i];
}
adc = (float)sum*5/256;
printf("sum=%d ad=%0.2f Vrn",sum,adc);
return adc;
}
- PWM信号初始化:
//arr为重载值
//psc为预分频系数
void Clock_PWM_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_DeInit(TIM3);
/* Time Base configuration */
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //TIM3_CH2
TIM_CtrlPWMOutputs(TIM3, ENABLE);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
TIM_SetCompare2(TIM3,arr/2);
}
main函数中调用如下:
Clock_PWM_Init(720-1,0); //PWM频率=72000/720 = 100Khz
- 代码运行:
1.6 ADC芯片AD5592应用
//代码索引:
void AD5592_Init(void);
void AD5592_IO_Config(void);
void AD5592_Write(uint16_t data_temp);
uint16_t AD5592_Read(u8 ch);
// SPI引脚定义
#define AD5592_CS PAout(5)
#define AD5592_CLK PAout(7)
#define AD5592_DIN PAout(8)
#define AD5592_DOUT PAin(11)
/***************************************************************************
** 函数名称 : AD5592_Init
** 功能描述 : AD5592芯片初始化
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210408
** 说 明 : 无
***************************************************************************/
void AD5592_Init(void)
{
AD5592_Write(0X7DAC); //芯片复位
delay_Xus1(250); // 延时250us
AD5592_IO_Config(); //SPI引脚配置
AD5592_Write(0X20FF); //配置所有端口为ADC输入
AD5592_Write(0X5AFF); //开启基准电压源V_REF
delay_Xus1(200);
}
/***************************************************************************
** 函数名称 : AD5592_IO_Config
** 功能描述 : AD5592 SPI通信引脚IO配置
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210408
** 说 明 : 注意芯片的输出SDO对应MCU的输入,芯片的输入SDI对应MCU的输出
***************************************************************************/
void AD5592_IO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// 输入配置
// SDO引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// 输出配置
// SYNC引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// SCLK引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// SDI引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
/***************************************************************************
** 函数名称 : AD5592_Write
** 功能描述 : AD5592 写数据
** 输入变量 : data_temp :要向AD5592寄存器写入的数据
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210408
** 说 明 : 无
***************************************************************************/
void AD5592_Write(uint16_t data_temp)
{
// u8 t = 0;
// AD5592_CS = 1;
// delay_Xus1(10);
// AD5592_CS = 0;
// delay_Xus1(5);
// AD5592_CLK = 0;
// for(t=0;t<16;t++)
// {
// if(((data_temp&0x8000)>>15)==1){AD5592_DIN = 1;}
// else {AD5592_DIN = 0;}
// AD5592_CLK = 0;
// delay_Xus1(5);
// AD5592_CLK = 1;
// delay_Xus1(5);
// data_temp<<=1;
// }
// AD5592_CLK = 0;
// delay_Xus1(5);
// AD5592_CLK = 1;
// delay_Xus1(5);
u8 t = 0;
AD5592_CS = 1;
delay_Xus1(20);
AD5592_CS = 0;
delay_Xus1(20);
for(t=0;t<16;t++)
{
AD5592_CLK = 1;
delay_Xus1(20);
if(((data_temp&0x8000)>>15)==1){AD5592_DIN = 1;}
else {AD5592_DIN = 0;}
delay_Xus1(20);
AD5592_CLK = 0;
delay_Xus1(20);
data_temp<<=1;
}
AD5592_CLK = 0;
delay_Xus1(20);
AD5592_CLK = 1;
delay_Xus1(20);
AD5592_CS = 1;
delay_Xus1(20);
}
/***************************************************************************
** 函数名称 : AD5592_Read
** 功能描述 : AD5592 读数据
** 输入变量 : ch :选择要读取的芯片端口(ADC)
** 返 回 值 : 返回读取的寄存器值
** 最后修改人 : xxx
** 最后更新日期: 20210408
** 说 明 : 注意返回值只是寄存器的值,而不是目标电压值
***************************************************************************/
uint16_t AD5592_Read(u8 ch)
{
uint16_t value_temp = 0;
uint16_t data_temp = 0;
u8 t=0;
if(ch==0){AD5592_Write(0x1001);} //选择AD0转换
if(ch==1){AD5592_Write(0x1002);} //选择AD1转换
if(ch==2){AD5592_Write(0x1004);} //选择AD2转换
if(ch==3){AD5592_Write(0x1008);} //选择AD3转换
if(ch==4){AD5592_Write(0x1010);} //选择AD4转换
if(ch==5){AD5592_Write(0x1020);} //选择AD5转换
if(ch==6){AD5592_Write(0x1040);} //选择AD6转换
if(ch==7){AD5592_Write(0x1080);} //选择AD7转换
//等待第二个始终周期
AD5592_CS = 1;
delay_Xus1(20);
AD5592_CS = 0;
delay_Xus1(20);
for(t=0;t<16;t++)
{
AD5592_CLK = 1;
delay_Xus1(20);
AD5592_CLK = 0;
delay_Xus1(20);
}
AD5592_CLK = 1;
delay_Xus1(20);
AD5592_CS = 1;
delay_Xus1(20);
AD5592_CS = 0;
delay_Xus1(20);
for(t=0;t<16;t++)
{
AD5592_CLK = 0;
delay_Xus1(20);
AD5592_CLK = 1;
delay_Xus1(20);
if(AD5592_DOUT==1)value_temp = value_temp|0x01;
value_temp = value_temp<<1;
}
AD5592_CS = 1;
delay_Xus1(20);
//转换结果取低12位
value_temp = value_temp&0xfff; //转换结果取低12位
return value_temp; //注意此结果只是寄存器的值,还要根据公式计算出实际的电压值;
}
1.7光强采集芯片BH1750FVI应用
上图是BH1750芯片的通信序列图,其可分为以下几个步骤:
- 发送上电命令:上电命令是0x01
- 发送测量命令:以发送的测量命令是“连续高分辨率测量(0x10)”为例,先是“起始信号(ST)”,接着是“器件地址+读写位”,然后是应答位,紧接着就是测量的命令“00010000”,然后应答,最后是“结束信号(SP)”。(相比于OPT3001的写入过程,BH1750少了一个发送寄存器地址的步骤,因为它只有一个寄存器,所以就没必要了)
- 等待测量结束:测量的时间手册上有,高分辨率连续测量需要等待的时间最长,手册上面写的是平均120ms,最大值180ms,所以为了保证每次读取到的数据都是最新测量的,程序上面可以延时200ms以上。
- 读取数据:先是“起始信号(ST)”,接着是“器件地址+读写位”,然后是应答位,紧接着接收1个字节的数据(单片机在这个时候要把SDA引脚从输出改成输入),然后给BH1750发送应答,继续接收1个字节数据,然后不应答(因为接收的数据只有2个字节,收完就可以结束通讯),最后是“结束信号(SP)”。
- 计算结果:光照强度 =(寄存器值[15:0] * 分辨率) / 1.2 (单位:勒克斯lx) . 因为从BH1750寄存器读出来的是2个字节的数据,先接收的是高8位[15:8],后接收的是低8位[7:0],所以需要先把这2个字节合成一个数,然后乘上分辨率,再除以1.2即可得到光照值。
例如:读出来的第1个字节是0x12(0001 0010),第2个字节是0x53(0101 0011),那么合并之后就是0x1253(0001
0010 0101 0011),换算成十进制也就是4691,乘上分辨率(1),再除以1.2,最后等于3909.17 lx。
BH1750所有指令如下图:
1.7.1 核心代码
// 代码索引:
void BH1750_StartSwitch(void);
double BH1750_ReadBasicData(void);
uint16_t BH1750_ReadData(void);
uint16_t BH1750_ReadData(void);
void BH1750_Power_On(void);
void BH1750_Clr_Data(void);
void BH1750_Mode_set(void);
void Strobe_led_state(void);
void Test_LED_Start(void);
void Test_LED(void);
void Read_LED(void);
void ReadLEDlightValue(void);
void Read_BH1750_Data(void);
void Clear_BH1750_Data(void);
参数定义:
#include "public.h"
extern u8 hsg_flag;
u32 SetLux;
uint32_t ALS_Value[5000];
u16 ALS_num;
u8 ALS_Read_Time;
u16 ALS_Delay_Time;
u8 GetLightVal;
double ALS_k=1.0;
double ALS_b=0.0;
double ALS_Cool_k=1.0;
double ALS_Cool_b=0.0;
double ALS_Warm_k=1.0;
double ALS_Warm_b=0.0;
extern u8 IIC_Channel;
extern uint16_t ALS_READ_TIME;
extern uint16_t ALS_DELAY_TIME;
extern uint32_t SetLux;
void BH1750_Power_On(void);
void BH1750_Clr_Data(void);
void BH1750_Mode_set(void);
void BH1750_StartSwitch(void);
uint16_t BH1750_ReadData(void);
uint32_t LuxValue;
/***************************************************************************
** 函数名称 : BH1750_StartSwitch
** 功能描述 : BH1750开始工作
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 : 此函数可以在采光开始时使用,若使用过程中出现采光不及时问题,可改为程序初始化时使用。
***************************************************************************/
void BH1750_StartSwitch(void)
{
BH1750_Power_On();
BH1750_Mode_set();
}
/***************************************************************************
** 函数名称 : BH1750_ReadBasicData
** 功能描述 : BH1750读取基本数据
** 输入变量 : 无
** 返 回 值 : 读到的数据
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 : 此函数未使用
***************************************************************************/
double BH1750_ReadBasicData(void)
{
u8 flag = 0;
u8 t=0;
u8 Data_H=0,Data_L=0;
float DATA=0.0;
u8 a,b,c,d,e,f,g,h;
u8 a1,b1,c1,d1,e1,f1,g1,h1;
IIC_Channel = 2;
do
{
IIC_Start();
IIC_WRITE_BYTE(0x47); //写从属地址
if(IIC_Recelve_Ack()==0)
{
Data_H = IIC_Read_Byte(1);
Data_L = IIC_Read_Byte(0);
flag = 0;
}
else {flag = 1;t++;}
}
while((flag==1)&&(t<250)); // 1111 1111 & 1000 0000 = 1000 0000
IIC_Stop();
// sprintfU3("DATA_H: %drn",Data_H);
// sprintfU3("DATA_L: %drn",Data_L);
a = (Data_H&0X80)>>7;
b = (Data_H&0X40)>>6;
c = (Data_H&0X20)>>5;
d = (Data_H&0X10)>>4;
e = (Data_H&0X08)>>3;
f = (Data_H&0X04)>>2;
g = (Data_H&0X02)>>1;
h = Data_H&0X01;
//
a1 = (Data_L&0X80)>>7;
b1 = (Data_L&0X40)>>6;
c1 = (Data_L&0X20)>>5;
d1 = (Data_L&0X10)>>4;
e1 = (Data_L&0X08)>>3;
f1 = (Data_L&0X04)>>2;
g1 = (Data_L&0X02)>>1;
h1 = Data_L&0X01;
DATA = pow(2*a,15.0)+pow(2*b,14.0)+pow(2*c,13.0)+pow(2*d,12.0)+pow(2*e,11.0)+pow(2*f,10.0)+pow(2*g,9.0)+pow(2*h,8.0)+
pow(2*a1,7.0)+pow(2*b1,6.0)+pow(2*c1,5.0)+pow(2*d1,4.0)+pow(2*e1,3.0)+pow(2*f1,2.0)+pow(2*g1,1.0)+h1;
DATA = DATA/1.2;
return (DATA);
}
/***************************************************************************
** 函数名称 : BH1750_ReadData
** 功能描述 : BH1750读取数据
** 输入变量 : 无
** 返 回 值 : 读到的数据
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
uint16_t BH1750_ReadData(void)
{
u8 t=0;
u8 flag = 0;
u8 Data_H=0,Data_L=0;
float DATA=0.0;
// u8 a,b,c,d,e,f,g,h;
// u8 a1,b1,c1,d1,e1,f1,g1,h1;
IIC_Channel = 2;
do
{
IIC_Start();
IIC_WRITE_BYTE(0x47); //写从属地址
if(IIC_Recelve_Ack()==0)
{
Data_H = IIC_Read_Byte(1);
Data_L = IIC_Read_Byte(0);
flag = 0;
}
else {flag = 1; t++;}
}
while((flag==1)&&t<250); // 1111 1111 & 1000 0000 = 1000 0000
IIC_Stop();
// sprintfU3("DATA_H: %drn",Data_H);
// sprintfU3("DATA_L: %drn",Data_L);
// a = (Data_H&0X80)>>7;
// b = (Data_H&0X40)>>6;
// c = (Data_H&0X20)>>5;
// d = (Data_H&0X10)>>4;
// e = (Data_H&0X08)>>3;
// f = (Data_H&0X04)>>2;
// g = (Data_H&0X02)>>1;
// h = Data_H&0X01;
// //
// a1 = (Data_L&0X80)>>7;
// b1 = (Data_L&0X40)>>6;
// c1 = (Data_L&0X20)>>5;
// d1 = (Data_L&0X10)>>4;
// e1 = (Data_L&0X08)>>3;
// f1 = (Data_L&0X04)>>2;
// g1 = (Data_L&0X02)>>1;
// h1 = Data_L&0X01;
//DATA = pow(2*a,15.0)+pow(2*b,14.0)+pow(2*c,13.0)+pow(2*d,12.0)+pow(2*e,11.0)+pow(2*f,10.0)+pow(2*g,9.0)+pow(2*h,8.0)+
// pow(2*a1,7.0)+pow(2*b1,6.0)+pow(2*c1,5.0)+pow(2*d1,4.0)+pow(2*e1,3.0)+pow(2*f1,2.0)+pow(2*g1,1.0)+h1;
//DATA = DATA/1.2*CoefficientLight;
DATA = Data_H<<8|Data_L;
DATA=DATA;
return ((uint16_t)DATA);
}
/***************************************************************************
** 函数名称 : BH1750_Power_On
** 功能描述 : 给BH1750芯片通电
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void BH1750_Power_On(void)
{
u8 flag = 0;
u8 t=0;
IIC_Channel = 2;
do
{
IIC_Start();
IIC_WRITE_BYTE(0x46); //写从属地址
if(IIC_Recelve_Ack()==0)
{
IIC_WRITE_BYTE(0x01); //上电指令
if(IIC_Recelve_Ack()==0)
{
flag = 0;
}
else {flag = 1;t++;}
}
else {flag = 1;t++;}
}
while((flag==1)&&t<250);
IIC_Stop();
delay_Nus(250);
delay_Nus(250);
delay_Nus(250);
delay_Nus(250);
}
/***************************************************************************
** 函数名称 : BH1750_Clr_Data
** 功能描述 : 清除BH1750芯片中的数据
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 : 此函数暂未使用
***************************************************************************/
void BH1750_Clr_Data(void)
{
u8 flag = 0;
u8 t=0;
IIC_Channel = 2;
do
{
IIC_Start();
IIC_WRITE_BYTE(0x46); //写从属地址
if(IIC_Recelve_Ack()==0)
{
IIC_WRITE_BYTE(0x07); //数据寄存器重置命令
if(IIC_Recelve_Ack()==0)
{
flag = 0;
}
else {flag = 1;t++;}
}
else {flag = 1;t++;}
}
while((flag==1)&&t<250);
IIC_Stop();
delay_Nus(250);
delay_Nus(250);
delay_Nus(250);
delay_Nus(250);
}
/***************************************************************************
** 函数名称 : BH1750_Mode_set
** 功能描述 : BH1750芯片参数设置
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void BH1750_Mode_set(void)
{
u8 flag = 0;
u8 t=0;
IIC_Channel = 2;
do
{
IIC_Start();
IIC_WRITE_BYTE(0x46); //写从属地址
if(IIC_Recelve_Ack()==0)
{
IIC_WRITE_BYTE(0x13); //连续低分辨率模式
if(IIC_Recelve_Ack()==0)
{
flag = 0;
}
else {flag = 1;t++;}
}
else {flag = 1;t++;}
}
while((flag==1)&&t<250);
IIC_Stop();
delay_Nus(250);
delay_Nus(250);
delay_Nus(250);
delay_Nus(250);
}
/***************************************************************************
** 函数名称 : Strobe_led_state
** 功能描述 : 读取光强状态并返回信息
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void Strobe_led_state(void)
{
uint8_t i=0;
uint32_t luxVal=0;
uint32_t luxresult=0;
IIC_Channel=2;
SetLux = IIC_Read_4Byte(Lux_Add,1);
ALS_Read_Time = IIC_Read_2Byte(Lux_Time_Add,1);
ALS_Delay_Time = IIC_Read_2Byte(Lux_Delay_Add,1);
BH1750_StartSwitch();
delay_ms(100);
delay_ms(ALS_Delay_Time);
if(h_flag==H_B)
{
printfU4("S Led Statern");
}
for(i=0;i<ALS_Read_Time;i++)
{
luxVal=BH1750_ReadData();
luxVal=ALS_k*luxVal+ALS_b;
delay_ms(ALS_Delay_Time);
sprintfU4("%drn",luxVal);
if(luxresult<luxVal) luxresult=luxVal;
}
if(h_flag==S_F)
{
// sprintfU4("Passrn@_@");
// }
// else
//{
if(luxresult>=SetLux)
{
sprintfU4("S LED On %d Passrn@_@",luxresult);
}
else
{
sprintfU4("S LED Off %d Passrn@_@",luxresult);
}
}
}
/***************************************************************************
** 函数名称 : Test_LED_Start
** 功能描述 : 开始读取光强
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void Test_LED_Start(void)
{
ALS_num=0;
memset(ALS_Value,0,sizeof(ALS_Value));
if(GetLightVal<=0)
{
LuxValue=0;
BH1750_StartSwitch();
delay_ms(150);
GetLightVal=1;
}
printfU4("Test Led Start Passrn@_@");
}
/***************************************************************************
** 函数名称 : Test_LED
** 功能描述 : 处理光强数据
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void Test_LED(void)
{
u16 i,j;
u32 temp;
GetLightVal=0;
if(ALS_num>5)
{
for(i=0;i<ALS_num-1;i++)
{
for(j=0;j<ALS_num-i;j++)
{
if(ALS_Value[j]<ALS_Value[j+1])
{
temp = ALS_Value[j];
ALS_Value[j] = ALS_Value[j+1];
ALS_Value[j+1] = temp;
}
}
}
LuxValue=(ALS_Value[0]+ALS_Value[1]+ALS_Value[2]+ALS_Value[3]+ALS_Value[4])/5;
}
else
{
//ALS_Value[0]=0;
LuxValue=0;
}
if((LuxValue>SetLux))
{
printfU4("Test LED Passrn@_@");
}
else
{
printfU4("Test LED Failrn@_@");
}
}
/***************************************************************************
** 函数名称 : Read_LED
** 功能描述 : 显示光强数据
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void Read_LED(void)
{
GetLightVal=0;
//sprintfU4("Read LED %d Passrn@_@",LuxValue);
//LuxValue=ALS_k*LuxValue+ALS_b;
if(ALS_num>5) sprintfU4("Read Led OK, the value is: %d luxrn@_@",LuxValue);
else sprintfU4("Read Led OK, the value is: %d luxrn@_@",ALS_Value[0]);
}
/***************************************************************************
** 函数名称 : ReadLEDlightValue
** 功能描述 : 扫描光强
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 : 此函数应该放在主函数中的循环中
***************************************************************************/
void ReadLEDlightValue(void)
{
uint32_t Tling=0;
if(GetLightVal==1)
{
Tling=BH1750_ReadData();
// sprintfU4(": %d luxrn@_@",Tling);
if(Tling>0)
{
ALS_Value[ALS_num++]=BH1750_ReadData();
}
else
{
ALS_Value[0]=0;
}
if(ALS_Delay_Time>0)delay_ms(ALS_Delay_Time);// MCU采集延迟速度
if(ALS_num>5000){GetLightVal=0;}
// if(Tling>LuxValue)
// {
// LuxValue=ALS_k*Tling;
// }
}
}
/***************************************************************************
** 函数名称 : Read_BH1750_Data
** 功能描述 : 打印BH1750读取到的数据
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void Read_BH1750_Data(void)
{
u16 i;
if(ALS_num>0)
{
for(i=0;i<ALS_num;i++)
{
sprintfU4("ALS_Value[%d] = %drn",i,ALS_Value[i]);
}
}
else printfU4("All BH1750 Data =0rn");
printfU4("Read BH1750 Data Passrn@_@");
}
/***************************************************************************
** 函数名称 : Clear_BH1750_Data
** 功能描述 : 清零BH1750读取到的数据
** 输入变量 : 无
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: xxx
** 说 明 :
***************************************************************************/
void Clear_BH1750_Data(void)
{
memset(ALS_Value,0,sizeof(ALS_Value));
ALS_num=0;
printfU4("Clear BH1750 Data Passrn@_@");
}
1.7.2 BH1750采集速度与MCU采集速度区别
BH1750对于不同的分辨率有不同的采样速率,比如连续低分辨率模式下最低采样速率为16ms,最高24ms,即在该模式下芯片最少每16ms更新一个数据。但是对于MCU从BH1750中读取数据的速度是根据程序而定的,在上述核心代码中的 ReadLEDlightValue()
函数中,有对MCU采样延时的功能。
比如MCU与BH1750同时开始各自的采样操作,MCU每1ms向BH1750读取一组数据,那么最少有16组数据是重复值,因为在此期间BH1750没有完成新的采样操作,即MCU一直读取的都是BH1750没有更新的值。
1.8 ADC芯片 - AD7780应用
datasheet:https://atta.szlcsc.com/upload/public/pdf/source/20200709/C651541_5E3E20831A51574C2A25451AAEBCECD9.pdf
AD7780 是ADI 24位ADC,其中最高为符号位,剩余23位位数据位,则有效数据位为 2^23-1 = 8388607 = 7F FFFF
,即ADC 数据满量程为83886087 = 7F FFFF .
假设加载在AD7780 上的参考电压为2.5V,那么测量输入AIN+、AIN- 端电压可以知道ADC 输入读取的模拟信号电压,若AIN+、AIN- 端电压为2.5V,表示已达满量程状态。
- 芯片初始化与参数定义:
#define AD7780_CLK(n) {if(n)GPIO_SetBits(GPIOA, GPIO_Pin_15);else GPIO_ResetBits(GPIOA, GPIO_Pin_15); } // AD7780 CLK 时钟引脚
#define AD7780_RST(n) {if(n)GPIO_SetBits(GPIOA, GPIO_Pin_12);else GPIO_ResetBits(GPIOA, GPIO_Pin_12); } // AD7780 PDRST 复位引脚
#define AD7780_DATA GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_10) // AD7780 DOUT/RDY 数据输出/数据准备完毕引脚
// #define AD7780_ADIN(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_12);else GPIO_ResetBits(GPIOC, GPIO_Pin_12);} // 无实际连接
#define AD7780_DCRSW(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_8);else GPIO_ResetBits(GPIOC, GPIO_Pin_8);} // ET4/ET6 继电器(0关1开)
#define AD7780_LSCSDCR(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_7);else GPIO_ResetBits(GPIOC, GPIO_Pin_7);}
#define CSDCR_SW1(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_11);else GPIO_ResetBits(GPIOC, GPIO_Pin_11);}
#define CSDCR_SW2(n) {if(n)GPIO_SetBits(GPIOD, GPIO_Pin_1);else GPIO_ResetBits(GPIOD, GPIO_Pin_1);}
extern u8 curr_Cyclone;
//float ResValue =0;
//float Res_OffValue = 0;
__IO float Res_mAValue = 50;
__IO float Res_mVValue = 2526;
__IO float DCR_Line_K = 1;
__IO float DCR_Line_B = 0;
__IO float SRDCR_Line_K = 1;
__IO float SRDCR_Line_B = 0;
//Cs DCR
__IO float resRefValue[3]= {499,10000,10000000};
void AD7780_IO_init(void)
{
u8 *p;
u8 temp = 0;
GPIO_InitTypeDef GPIO_InitStructure; //GPIO
/* 打开GPIO时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_15;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_5;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_11|GPIO_Pin_12;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOC, &GPIO_InitStructure);
AD7780_ADIN(1);//OFF SIGNAL IN
//AD7780_GAIN(1);
AD7780_DCRSW(0);//OFF DCR FUCTION
AD7780_LSCSDCR(0);//DCR CONNECT TO LS
CSDCR_SW1(0);
CSDCR_SW2(0);
temp = AT24CXX_ReadOneByte(AD7780_FLAG_Addr);
if(temp==0x0A)
{
AD7780_ReadConfig();
}
else
{
CLearAD7780Config();
AD7780_WriteConfig();
}
AD7780_Reset();
}
1.8.1 AD7780 小电阻与大电阻测量方法
一般小电阻采用恒流源测试精度更高; 大电阻采用分压电路 + 分挡位测试精度更高、范围更大;
- 小电阻恒流源方法测试代码:
#define AD7780_CLK(n) {if(n)GPIO_SetBits(GPIOA, GPIO_Pin_15);else GPIO_ResetBits(GPIOA, GPIO_Pin_15); }
#define AD7780_RST(n) {if(n)GPIO_SetBits(GPIOA, GPIO_Pin_12);else GPIO_ResetBits(GPIOA, GPIO_Pin_12); }
#define AD7780_DATA GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_10)
#define AD7780_ADIN(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_12);else GPIO_ResetBits(GPIOC, GPIO_Pin_12);}
#define AD7780_DCRSW(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_8);else GPIO_ResetBits(GPIOC, GPIO_Pin_8);}
#define AD7780_LSCSDCR(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_7);else GPIO_ResetBits(GPIOC, GPIO_Pin_7);}
// 读取DCR 值
float Read_LSDcr(void) //mOhm
{
float ResValue=-1.0;
COIL1_TO_COIL2;
AD7780_DCRSW(1); //ON DCR TEST
AD7780_LSCSDCR(0); //CONNECT TO LS DCR
AD7780_ADIN(0); //ON SIGNAL IN
while(ResValue<0)
{
switch(LCR_BOARD_TYPE)
{
case 1:
case 2:ResValue = 1000.0*Res_mVValue*(AD7780_Read_NEW()&0x7FFFFF)/(8388608.0)/Res_mAValue;break;//CONFIRM CONNECT NORMAL、AD7780_Read_NEW() 就是调用多次AD7780_Read()
default:ResValue = 1000.0*Res_mVValue*(AD7780_Read()&0x7FFFFF)/(8388608.0)/Res_mAValue;break;
// 1. 首先调用ad7780_read()函数读取AD7780芯片的输出值,该值是一个24位的有符号整数。
// 2. 通过按位与运算符&和0x7fffff,将该值的最高位符号位去掉,得到一个23位的无符号整数。
// 3. 将该值除以8388608,得到一个0到1之间的小数,表示AD7780芯片输出值所代表的电压值。
// 4. 将该小数乘以10000、res_mvvalue和res_mavalue三个系数,得到最终的电压值,单位为毫伏。
}
}
ResValue = DCR_Line_K * ResValue + DCR_Line_B; // 计算K、B值
AD7780_ADIN(1); //OFF SIGNAL IN
AD7780_DCRSW(0); //OFF DCR TEST
AD7780_LSCSDCR(0); //CONNECT TO LS DCR
return ResValue;
}
uint32_t AD7780_Read(void)
{
uint32_t total=0;
uint32_t data[10];
u8 i,j;
for(i=0; i<10; i++)
{
data[i]= AD7780_ReadAd();
if(data[i]&&0x80==0&&i>0)i--; //V0x0080008D
}
return data[9];
for(i=0;i<9;i++) // 循环取10 次数据,并进行冒泡排序
{
for(j=i+1;j<10;j++)
{
if(data[j]<data[i])
{
total = data[i];
data[i] = data[j];
data[j] = total;
}
}
}
total=0;
for(i=2;i<7;i++)total+=data[i]; // 冒泡后,取其中第2到第6个数据求和,再求平均
return (total/5);
}
uint32_t AD7780_ReadAd(void)
{
static unsigned char adc_err_cnt;
uint8_t i;
uint32_t t=0;
AD7780_CLK(1);
bsp_DelayUS(10);
while(AD7780_DATA) {} //等待数据转换完成
bsp_DelayUS(10);
for(i=0,t=0; i<32; i++)
{
AD7780_CLK(0);
bsp_DelayUS(15);
AD7780_CLK(1);
bsp_DelayUS(15);
t <<=1;
if(AD7780_DATA)t++;
}
if((t&0xff)==0x4d)
{
t>>=8;
return t;//t&0x7FFFFF;
}
else
{
adc_err_cnt++;
if(adc_err_cnt>5)AD7780_Reset();
return t>>8;
}
}
- 大电阻分挡分压方法测试代码:
__IO float resRefValue[3]= {499,10000,10000000}; // 三个上拉电阻挡位
#define COIL1_TO_GND {GPIO_SetBits(GPIOC, GPIO_Pin_6);GPIO_SetBits(GPIOC, GPIO_Pin_9);GPIO_ResetBits(GPIOD, GPIO_Pin_15);GPIO_ResetBits(GPIOA, GPIO_Pin_8);} // setbit即打开继电器
#define COIL2_TO_GND {GPIO_SetBits(GPIOC, GPIO_Pin_6);GPIO_SetBits(GPIOC, GPIO_Pin_9);GPIO_SetBits(GPIOD, GPIO_Pin_15);GPIO_ResetBits(GPIOA, GPIO_Pin_8);}
#define AD7780_ADIN(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_12);else GPIO_ResetBits(GPIOC, GPIO_Pin_12);}
#define AD7780_DCRSW(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_8);else GPIO_ResetBits(GPIOC, GPIO_Pin_8);}
#define AD7780_LSCSDCR(n) {if(n)GPIO_SetBits(GPIOC, GPIO_Pin_7);else GPIO_ResetBits(GPIOC, GPIO_Pin_7);}
float Read_CSDCR(u8 ch) //ohm
{
float value;
OpenCsDCRTest(ch);
value= Read_CSDCRByCh(2);
if(value>400000) {
if(value>10000000)value=10000000;
}
else
{
value= Read_CSDCRByCh(1);
if(value<3000) {
value = Read_CSDCRByCh(0);
}
}
CloseCsDCRTest(ch);
return value;
}
void OpenCsDCRTest(u8 ch)
{
if(ch==1) {
COIL1_TO_GND;
}
else if(ch==2) {
COIL2_TO_GND;
}
AD7780_DCRSW(1); //ON DCR TEST
AD7780_LSCSDCR(1); //CONNECT TO CS DCR
AD7780_ADIN(0); //ON SIGNAL IN
bsp_DelayMS(100);
}
float Read_CSDCRByCh(u8 ch) //ohm
{
float CSDcrValue;
u32 AD_Value;
if(ch==2)
{
CSDCR_SW1(1);
CSDCR_SW2(1);
bsp_DelayMS(100);
AD_Value = AD7780_Read();
if(AD_Value & 0x800000)
AD_Value &= 0x7fffff;
else
AD_Value = 0;
CSDcrValue = 1.0* AD_Value*resRefValue[2]/(8388608.0-AD_Value);
}
if(ch==1)
{
CSDCR_SW1(0);
CSDCR_SW2(1);
bsp_DelayMS(100);
AD_Value = AD7780_Read();
if(AD_Value & 0x800000)
AD_Value &= 0x7fffff;
else
AD_Value = 0;
CSDcrValue = 1.0* AD_Value*resRefValue[1]/(8388608.0-AD_Value);
}
if(ch==0)
{
CSDCR_SW1(1);
CSDCR_SW2(0);
bsp_DelayMS(100);
AD_Value = AD7780_Read();
if(AD_Value & 0x800000)
AD_Value &= 0x7fffff;
else
AD_Value = 0;
CSDcrValue = 1.0* AD_Value*resRefValue[0]/(8388608.0-AD_Value);
}
return CSDcrValue;
}
1.9 解决ADC 分辨率不够导致测试进度不够的解决方案
在软件中对ADC 进行多次采集,多个数据求平均值,可解决ADC 分辨率不够导致测试进度不够的问题;
2. 存储
2.1 EEPROM芯片 - AT24Cxx
AT24Cxx常用的IIC通讯的EEPROM 器件;
2.1.1 写操作
在该模式下,主器件发送IIC起始信号(通知从器件开始工作)和从器件地址信息(选择与哪个从器件进行通信)给从器件,从器件回应主器件以应答信号后,主器件发送CAT24WC01/02/04/08/16的字节地址(EEPROM内存储单元的地址),从器件回应主器件以应答信号后,主器件发送要写入的数据到被寻址的存储单元,CAT24WC01/02/04/08/16回应主器件以应答信号后,主器件发送IIC停止信号(通知从器件停止工作)给从器件。
页写操作同理于字节写操作,只是写入一个字节后不产生停止信号,主器件被允许发送P个额外的字节(CAT24WC01:P=7,CAT24WC02/04/08/16:P=15,即使用写页操作下,CAT24WC0一次可写8个字节,CAT24WC02/04/08/16一次可写16个字节)。
注意,若在发送停止信号前主器件发送的字节超过P+1个,地址计数器将自动翻转,先前写入的数据将被覆盖。
2.1.2 读操作
在该模式下,主器件发送IIC起始信号(通知从器件开始工作)和从器件地址信息(选择与哪个从器件进行通信)给从器件,从器件回应主器件以应答信号后,主器件发送CAT24WC01/02/04/08/16的字节地址(EEPROM内存储单元的地址),CAT24WC01/02/04/08/16回应主器件以应答信号后,从器件向主器件返回所要求的一个字节数据,此后主器件不发送应答,但产生一个停止信号。
页读操作就是在字节读操作读完一个字节后,主器件不发送停止信号给从器件,而是发送一个应答信号表示要求进一步读取下一个字节信号,直到主器件向从器件发送停止信号。
注意,若主器件读取的字节超过E个,地址计数器将自动翻转,计数器将翻转到零并继续输出字节数据。(24WC01,E=127;24WC02,E=255;24WC04,E=511;24WC08,E=1023;24WC16,E=2047;)
2.1.3 核心代码
以下代码理论上支持24Cxx系列芯片(地址引脚设置为0),24Cxx的型号定义可在头文件24cxx.h
中查看。
函数索引:
void AT24CXX_Init(void); // 芯片初始化
u8 AT24CXX_ReadOneByte(u16 ReadAddr); // 读一个字节数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); // 写一个字节数据
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len); // 写多个字节数据
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len); // 读多个字节数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead); // 读任意位数据
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite); // 写任意位数据
u8 AT24CXX_Check(void); // 检查芯片是否正常工作
void IIC_Write_4Byte(unsigned int RomAddress,unsigned int udata,u8 ch); //连续写四个字节数据
unsigned int IIC_Read_4Byte(unsigned int RomAddress,u8 ch); //连续读写入四个字节数据
void IIC_Write_double(unsigned int RomAddress,double udata,u8 ch); // 写一个双精度浮点数数据
double IIC_Read_double(unsigned int RomAddress,u8 ch); // 读一个双精度浮点数数据
void AT24CXX_ReadFloat(u16 ReadAddr,u8 *p); //读一个浮点数数据
void AT24CXX_WriteFloat(u16 WriteAddr,u8 *p); // 写一个浮点数数据
void IIC_Write_Nbyte(unsigned char *pc,unsigned int Addr,unsigned char number,u8 ch); // 写多个字节数据
void IIC_Read_Nbyte(unsigned char *pc,unsigned int Addr,unsigned char number,u8 ch); // 读多个字节数据
void testCode_writeEeprom(void); // 数据写入测试代码
void testCode_readEeprom(void); // 数据读取测试代码
- 初始化24Cxx
// 即初始化IIC
void AT24CXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO
/* 打开GPIO时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOB, ENABLE);
/* 配置芯片IIC引脚 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(IIC_SDA1_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(IIC_SCL1_PORT, &GPIO_InitStructure);
}
- 在24Cxx的指定地址中读取一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8); //发送高地址
}
else
IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址 0XA0,写数据
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(0XA1); //进入接收模式
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop(); //产生一个停止条件
return temp;
}
- 在 24Cxx的指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr>>8);//发送高地址
}
else
IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址 0XA0,写数据
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite); //发送字节
IIC_Wait_Ack();
IIC_Stop(); //产生一个停止条件
delay_ms(10);
}
- 在24Cxx的指定地址写入长度为 Len 的数据
//该函数用于写入Len个字节的数据.
//WriteAddr :开始写入的地址
//DataToWrite:数据数组首地址,,最大支持32位
//Len :要写入数据的字节数(8位为一个字节)
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{
u8 t;
for(t=0;t<Len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}
- 在24Cxx的指定地址读出长度为 Len 的数据
//该函数用于读出Len个字节的数据.
//ReadAddr :开始读出的地址
//返回值 :数据,最大支持32位
//Len :要读出数据的字节数
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{
u8 t;
u32 temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
- 在24Cxx的指定地址读出指定个数的数据
//ReadAddr :开始读出的地址 对 24c02 为 0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
- 在24Cxx的指定地址写入指定个数的数据
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
- 检查24Cxx是否正常工作
//这里用了 24XX 的最后一个地址(第255个字节)来存储标标志.
//如果用其他 24C 系列,这个地址要修改
//返回 1:检测失败
//返回 0:检测成功
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255); //避免每次开机都写 AT24CXX
if(temp==0X55)return 0;
else //排除第一次初始化的情况
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)
return 0;
}
return 1;
}
- 在指定地址连续写入4个字节数据
void IIC_Write_4Byte(unsigned int RomAddress,unsigned int udata,u8 ch)
{
unsigned char i;
IIC_Channel = ch;
for(i=0;i<=24;i+=8)
{
AT24CXX_WriteOneByte(RomAddress,((unsigned char)(udata>>i)),IIC_Channel);
RomAddress++;
}
}
- 在指定地址连续读取4个字节数据
unsigned int IIC_Read_4Byte(unsigned int RomAddress,u8 ch)
{
unsigned char BufData,i;
unsigned long data=0;
IIC_Channel = ch;
for(i=0;i<=24;i+=8)
{
BufData=AT24CXX_ReadOneByte(RomAddress,IIC_Channel);
RomAddress++;
data|=((unsigned long)BufData)<<i;
}
return data;
}
- 在指定地址写入一个双精度浮点数数据
void IIC_Write_double(unsigned int RomAddress,double udata,u8 ch)
{
unsigned char i;
void *p;
IIC_Channel = ch;
p=&udata;
for(i=0;i<sizeof(double);i++)
{AT24CXX_WriteOneByte(RomAddress+i,*((char *)p+i),IIC_Channel);}
}
- 在指定地址读取一个双精度浮点数数据
double IIC_Read_double(unsigned int RomAddress,u8 ch)
{
unsigned char i;
double data=0.0;
void *p;
IIC_Channel = ch;
p=&data;
for(i=0;i<sizeof(double);i++)
{*((char *)p+i)=AT24CXX_ReadOneByte(RomAddress+i,IIC_Channel);}
return data;
}
- 在指定地址读取/写入一个浮点数数据
#define FLOAT_BYTE_NUM 4 // float类型占用的字节数
// #define FLOAT_BYTE_NUM sizeof(float)
// *p:一个指向float类型变量的指针
void AT24CXX_ReadFloat(u16 ReadAddr,u8 *p)
{
u8 t;
for(t=0; t<FLOAT_BYTE_NUM; t++)
{
*(p+3-t)=AT24CXX_ReadOneByte(ReadAddr+3-t); // 将读取到的数据保存到指针p指向的数据
}
}
void AT24CXX_WriteFloat(u16 WriteAddr,u8 *p)
{
u8 t;
for(t=0; t<FLOAT_BYTE_NUM; t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,*(p+t));
}
}
应用举例:
float floatData=1.0;
u8 *p;
p = (u8 *)(&floatData);
AT24CXX_WriteFloat(floatDataAddr,p);
AT24CXX_ReadFloat(floatDataAddr,p);
- 在指定地址连续写入N个字节数据
void IIC_Write_Nbyte(unsigned char *pc,unsigned int Addr,unsigned char number,u8 ch)
{
unsigned char i;
IIC_Channel = ch;
for(i=0;i<=number;i++)
{
AT24CXX_WriteOneByte(Addr,pc[i],IIC_Channel);
Addr++;
}
}
- 在指定地址连续读取N个字节数据
void IIC_Read_Nbyte(unsigned char *pc,unsigned int Addr,unsigned char number,u8 ch)
{
unsigned char i;
IIC_Channel = ch;
for(i=0;i<number;i++)
{
pc[i]=AT24CXX_ReadOneByte(Addr,IIC_Channel);
Addr++;
}
}
- 应用例程
const u8 TEXT_Buffer[]={"somthing data"}; // 数据缓冲区
#define SIZE sizeof(TEXT_Buffer) // 数据长度
int main(void)
{
AT24CXX_Init();
LED_Init();
while(AT24CXX_Check()) //检测24Cxx是否正常工作,若正常则跳出该循环
{
LED0=!LED0; //LED闪烁
}
AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE) // 在24Cxx的指定地址写入指定个数据
AT24CXX_Read(0,datatemp,SIZE); //在24Cxx的指定地址读取指定个数据
}
- EEPROM数据写入/读取测试代码:
void testCode_writeEeprom(void)
{
unsigned long data = 4294967295;
AT24CXX_WriteLenByte(Name_Add,data,sizeof(data),1);
sprintfU4("size of data(byte) : %drn", sizeof(data));
}
void testCode_readEeprom(void)
{
unsigned long data;
sprintfU4("size of data(byte) : %drn", sizeof(data));
data = AT24CXX_ReadLenByte(Name_Add,sizeof(data),1);
sprintfU4("data : %lurn", data);
}
2.2 延长EEPROM寿命
对于Flash或EEPROM,读操作对其寿命影响不大,写操作(对Nand Flash则为块擦除)次数基本决定了存储器寿命;而且写入寿命在每个位之间是独立的。延长EEPROM寿命有以下几种方法:
- 不固定数据存放的地址,而是用一个固定的基地址加上EEPROM内的一个单元的内容(即偏移地址)作为真正的地址;若发现存储单元已坏(写入和读出的内容不同),则偏移地址加1,重新写入。此方法有一弊端:当某一个EEPROM单元被写坏再用下一个单元时,后面到所有数据都会被覆盖。
- 从第一个存储单元开始存储数据N次,然后转到下一个单元再存N次,依次类推,当最后一个单元存放N次之后,再转到第一个单元重新开始。即不重复读写(擦写)某几个存储单元,尽量用到EEPROM上的所有存储单元,防止某几个存储单元反复擦写导致损坏。
- 在每次写入EEPROM时,附带写入一个标志位。
AT24CXX_WriteOneByte(DATA_FLAG_Addr,0x0A);
,上电初始化EEPROM时,读取该位并进行比较,查看EEPROM是否被写入过。(其实该方法对EEPROM对寿命对延长作用不大,更多的是处理初始化数据的作用)
temp = AT24CXX_ReadOneByte(DATA_FLAG_Addr);
if(temp==0x0A)
{
// 读取EEPROM中的数据,并写入到程序到变量中
}
else
{
// 手动写入一些初始值到程序变量中
}
2.3 关于存储器读写地址
存储器写入首先不能超过存储器的最高数据位,如AT24C02是2k(256*8)即2k bit(256个字节)的存储芯片,其最高字节地址为256,最后一个位的地址为2048. 其次是每个地址要定义好用于存储多少位的数据,千万不能产生数据存储错乱,比如定义为用于存储一个字节数据的地址,却写入了两个字节数据,那么不仅在下次读该字节地址中的值产生错误,还会导致多出的一个字节数据写入到下一个字节数据地址里去,造成下一个字节数据地址的数据错乱。
#define xxx0_Addr 0 // xxx0_Addr该地址用于存储一个字节的数据
#define xxx1_Addr 8 // 那么下一个字节数据就要相隔一个字节,xxx1_Addr 该地址用于存储两个字节的数据
#define xxx1_Addr 16 // 那么下一个字节数据就要相隔两个字节,如此类推
参考:AT24C02使用详解
3. 中断
中断是指通过硬件来改变CPU 的运行方向。单片机在执行程序的过程中,外部设备向CPU 发出中断请求信号,要求CPU 暂时中断当前程序的执行而转去执行相应的处理程序,待处理程序执行完毕后,再继续执行原来被中断的程序。这种程序在执行过程中由于外界的原因而被中间打断的情况称为“中断”;
- 主程序:原来正常运行的程序
- 中断源:引起中断的原因,或者能发出中断请求的来源
- 中断请求:中断源要求服务的请求称为中断请求(或中断申请)
- 断点:主程序被断开的位置(或者地址)
- 中断入口地址:中断服务程序的首地址
- 中断服务程序:cpu响应中断后,转去执行的相应处理程序
- 中断的特点:
- 同步工作:中断是CPU 和接口之间的信息传递方式之一,它使CPU 与外设同步工作,较好地解决了快速CPU 与慢速外设之间的匹配问题;
- 异常处理:针对难以预料的异常情况,如掉电、存储出错、运算溢出等,可以通过中断系统由故障源向CPU 发出中断请求,再由CPU 转到相应的故障处理程序进行处理;
- 实时处理:CPU 能够及时处理应用系统的随机事件,实时性大大增加;
3.1 STM32 的中断
Cortex-M3(CM3)内核MCU 最多支持256个中断,其中包含了16 个内核中断和240个可屏蔽中断,最多具有 256 级的可编程中断设置,根据不同型号单片机,其支持的中断数量不同,具体可查看对应芯片的数据手册中的中断向量表:
如::STM32F1 系列芯片只用了CM3内核的部分资源,共有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级;而STM32F103系列又只有60个可屏蔽中断,如下图:
中断优先级数字越小,对应中断优先级越高;
STM32 外部中断功能实现细分如下图:
下面进行逐一讲解:
3.1 NVIC 嵌套向量中断控制器
NVIC(嵌套向量中断控制器) 控制整个芯片中断的相关功能;
3.1.1 中断优先级分组(中断管理方法)
STM32F1系列芯片通过配置应用中断与复位控制寄存器Application interrupt and reset control register AIRCR[10:8]
来对MCU 的中断优先级分组进行配置,中断优先级分组的作用就是决定把IP bit[7:4]
这4个位如何分配给抢占优先级和子优先级;
- 配置中断优先级的功能通过函数
NVIC_PriorityGroupConfig()
实现,它定义在源文件misc.c
中,其函数定义如下:
/**
* @brief Configures the priority grouping: pre-emption priority and subpriority.
* @param NVIC_PriorityGroup: specifies the priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority 级别最高
* 4 bits for subpriority
* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
* 3 bits for subpriority
* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
* 2 bits for subpriority
* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
* 1 bits for subpriority
* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
* 0 bits for subpriority
* @retval None
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
// 如将MCU 的中断优先级分组配置为组0,即IP bit 第4~ 7位为0 位抢占优先级,4位响应优先级:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
中断优先级分组配置应该在MCU 初始化的同时配置好,中途不能对其进行修改,以免造成中断混乱;
- 抢占优先级和响应优先级的区别:
- 抢占 = 打断别人,高优先级的抢占优先级可以打断正在进行的低抢占优先级中断(值越小说明级越高);
- 响应 = 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;
- 若需要挂起/解挂中断,查看中断当前激活状态,分别调用相关函数;
应用举例:
- 假设MCU 设置中断优先级组为2,然后对以下三个中断进行配置:
- 中断4(FLASH中断 )抢占优先级为2,响应优先级为1
- 中断5(RCC中断)抢占优先级为3,响应优先级为0
- 中断6(EXTIO中断) 抢占优先级为2,响应优先级为0
则,3个中断的优先级顺序为: 中断6>中断4>中断5
3.1.2 NVIC 参数结构体
NVIC的所有需要配置的参数都列举在结构体NVIC_InitTypeDef
中,它定义在源文件misc.c
中,其结构体如下:
/**
* @brief NVIC Init Structure definition
*/
typedef struct
{
uint8_t NVIC_IRQChannel; /* 中断源 !< Specifies the IRQ channel to be enabled or disabled.
This parameter can be a value of @ref IRQn_Type
(For the complete STM32 Devices IRQ Channels list, please
refer to stm32f10x.h file) */
uint8_t NVIC_IRQChannelPreemptionPriority; /* 抢占优先级 !< Specifies the pre-emption priority for the IRQ channel
specified in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
uint8_t NVIC_IRQChannelSubPriority; /* 子优先级 !< Specifies the subpriority level for the IRQ channel specified
in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
FunctionalState NVIC_IRQChannelCmd; /* 中断源使能 !< Specifies whether the IRQ channel defined in NVIC_IRQChannel
will be enabled or disabled.
This parameter can be set either to ENABLE or DISABLE */
} NVIC_InitTypeDef;
3.2 外部中断/事件控制器 EXTI
EXTI(External interrupt/event controller - 外部中断/事件控制器):管理着中断控制器的 20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可实现对每个中断/事件线进行单独配置,可单独配置为中断或者事件,以及触发事件的属性。EXTI 是NVIC的一个中断/事件传入器;
-
EXTI 可分为两大部分功能:
- 产生中断:如下图,红色线路1-2-4-5是产生中断的过程
- 产生事件:如下图,绿色线路1-2-3-6-7-8是产生事件的过程,其中标黄的 20/ 代表着有20条相同的线路
-
EXTI功能框图:
关于EXTI 的代码都在固件库的 stm32f10x_exti.h
和 stm32f10x_exti.c
文件中;
STM32 的每个 IO 都可以作为外部中断的中断输入口,比如STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断/事件都有独立的触发和屏蔽设置。
其中,STM32F103 的19 个外部中断为:
- 线 0~15:对应外部 IO 口的输入中断。
- 线 16:连接到 PVD 输出。
- 线 17:连接到 RTC 闹钟事件。
- 线 18:连接到 USB 唤醒事件。
可见,stm32f103中外部IO 口的输入中断有16条线,但STM32 的GPIO却远远不止16个,其分配方式如下图:
以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0 . 而中断线每次只能连接到 1个 IO 口上,这就需要通过配置来决定对应的中断线配置到哪个 GPIO 上;
-
STM32 EXTI 中断向量表:
从上图可知,IO 口外部中断在中断向量表中只分配了7个中断向量也就是只能使用7个中断服务函数,从表中可以看出,外部中断线5~ 9分配一个中断向量,共用一个服务函数;外部中断线10~ 15分配一个中断向量,共用一个中断服务函数; -
对于每个中断线,可以设置相应的触发方式:上升沿触发,下降沿触发,边沿触发;
3.3 中断服务函数
中断被成功出发后,代码就会执行中断服务函数中的代码。
每个中断都有其固定的中断服务函数名,只有在这个函数名下编写中断服务函数才是有效的。所有中断服务函数都可在stm32f10x_it.c
的中断向量表中查找。其中EXTI线0到EXTI线4线都是单独的中断函数名、EXTI线5到EXTI线9共用一个中断函数名、EXTI线10线到EXTI线15线共用一个中断函数名。
// 例程
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(KEY1_EXTI_LINE)!=RESET) // 读取中断是否执行
{
LED1_TOGGLE; //LED1的亮灭状态反转
}
EXTI_ClearITPendingBit(KEY1_EXTI_LINE); //清除中断标志位
}
在 《STM32中文参考手册 V10》 - 第九章的表55 其它STM32F10xxx产品(小容量、中容量和大容量)的向量表
中可查看所有的中断通道;
3.4 STM32 中断优先级寄存器配置及其参考代码
STM32 中断优先级配置一共设计以下7个寄存器:
SCB_AIRCR
:32 位寄存器,有效位为第8到10位,用于设置5种中断优先级分组;IP
:240个8位寄存器,每个中断使用一个寄存器来确定优先级,每个8位寄存器有效位为第4到7位,用于设置抢占优先级与响应优先级;
如STM32F10x系列一共60个可屏蔽中断,那么它就只使用了IP[59]~IP[0l;
ISER
:8个32位寄存器,每个位控制一个中断的使能,写1
使能,写0
无效;
如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ISER[0]和ISER[1]; ISER[0]的bito~ bit31分别对应中断0~ 31、ISER[1]的bit0~ bit27对应中断32~ 59;
ICER
:8个32位寄存器,每个位控制一个中断的失能,写1
失能,写0
无效;
如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ICER[0]和ICER[1]; ICER[0]的bito~ bit31分别对应中断0~ 31、ICER[1]的bit0~ bit27对应中断32~ 59;
ISPR
:8个32位寄存器,每个位控制一个中断的挂起,写1
失能,写0
无效;
如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ISPR[0]和ISPR[1]; ISPR[0]的bito~ bit31分别对应中断0~ 31、ISPR[1]的bit0~ bit27对应中断32~ 59;
ICPR
:8个32位寄存器,每个位控制一个中断的解挂,写1
失能,写0
无效;
如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ICPR[0]和ICPR[1]; ICPR[0]的bito~ bit31分别对应中断0~ 31、ICPR[1]的bit0~ bit27对应中断32~ 59;
IABR
:8个32位寄存器,只读寄存器,每个位指示一个中断的激活状态,读1
表示中断正在执行;
如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的IABR[0]和IABR[1]; IABR[0]的bito~ bit31分别对应中断0~ 31、IABR[1]的bit0~ bit27对应中断32~ 59;
- 参考代码:以配置中断5作为外部中断为例,GPIO 口选择PE9:
- 输入配置为浮空输入
- 若涉及到端口复用问题,还需要打开相应的端口复用时钟
void EXTI_Configuration(void) // 配置外部中断/事件
{
EXTI_InitTypeDef EXTI_InitStructure; // 定义外部中断参数结构体
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource9); // 外部中断线配置,PE9
EXTI_InitStructure.EXTI_Line=EXTI_Line9; // 外部中断线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 外部中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能外部中断
EXTI_Init(&EXTI_InitStructure); //初始化外部中断线参数
}
void NVIC_Configuration(void) // 配置NVIC
{
NVIC_InitTypeDef NVIC_InitStructure; // 定义中断配置参数结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); // 设置中断组为0
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; // 设置中断来源(中断通道) ,外部中断线5
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; // 设置抢占优先级为 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3; // 设置子优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure); //初始化NVIC参数
}
void EXTI_init(void) //外部中断初始化
{
NVIC_Configuration();
EXTI_Configuration();
}
void EXTI9_5_IRQHandler(void) // 外部中断5 中断服务函数,记得中断服务函数的函数名是内核规定的,不是自定义的!
{
if(EXTI_GetITStatus(EXTI_Line9) != RESET) // 注意这里不要写错NVIC的 EXTI9_5_IRQn !!
sprintfU4("外部中断5已被触发rn");
EXTI_ClearITPendingBit(EXTI_Line9); //清除 LINE9 上的中断标志位
}
3.5 其他:
- 基于CORTEX-M3内核的硬件因素,清除中断标志不会马上生效,需要一段时间,如果你的中断服务程序时间很短,就会出现中断重复进入的异常;这种情况,可以在程序中增加去抖动和延时功能;
参考:STM32 官方参考手册;
最后
以上就是美满黑裤为你收集整理的STM32理论 —— ADC、存储、定时器、时钟、中断1. ADC2. 存储3. 定时器4. 时钟5. 中断的全部内容,希望文章能够帮你解决STM32理论 —— ADC、存储、定时器、时钟、中断1. ADC2. 存储3. 定时器4. 时钟5. 中断所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复