STM32理论 —— ADC、存储、中断

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_SQRxADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIEJEOCIE,只在最后一个通道转换完毕后才会产生 EOCJEOC 中断
  • 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.cstm32f10x_adc.h 文件中。

以STM32F103ZET中 ADC1 的通道 1 进行 AD 转换为例:

  1. 使用 ADC1 的通道 1 进行 AD 转换: 已知ADC 通道 1 在 PA1 上,所以先要使能 GPIOA 的时钟和 和 ADC1时钟,然后设置 PA1 为模拟输入。使能 GPIOA 和 ADC 时钟用 RCC_APB2PeriphClockCmd 函数,设置 PA1 的输入方式,使用GPIO_Init 函数即可。
  2. 复位 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);
  1. 初始化 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;
  1. 使能 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 准结束
  1. 读取 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 %dmV\r\n@_@",adcx);
	  sprintfU3("%dmV\r\n@_@",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也需要一个触发信号来实行模/数转换。

  1. 直接通过配置 控制寄存器2(CR2) 的ADON位(使能AD转换器),写1时开始转换,写0时停止转换。
  2. 通过内部定时器或者外部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方向
STARTPA2输出
EOCPA3输入
OEPA4输出
CLOCKPA7输出
ALEPA6输出
ADD APA5输出
ADD BPB10输出
ADD CPB11输出
ADC0809_D0(最低位)PA11输入
ADC0809_D1PA12输入
ADC0809_D2PC10输入
ADC0809_D3PC11输入
ADC0809_D4PC12输入
ADC0809_D5PD2输入
ADC0809_D6PB13输入
ADC0809_D7(最高位)PB12输入
  • 时序图:
    在这里插入图片描述

1.5.2 工作过程

  1. 控制与ADD A~ADD C相连的引脚,选择一个模拟输入端;
    在这里插入图片描述

  2. CLOCK端输入一个时钟信号,这里通过STM32的PWM实现此时钟脉冲,脉冲频率100 KHz;

  3. 将ALE由低电平置为高电平,从而将ADD A-ADD C送进的通道锁存,经译码后被选中的通道的模拟量送给内部转换单元;

  4. 给START一个正脉冲。当上升沿时,所有内部寄存器清零。下降沿时,开始进行A/D转换;在转换期间,START保持低电平;

  5. 读取EOC引脚的状态,A/D转换期间,EOC输入低电平;A/D转换结束,EOC引脚输入高电平;

  6. 当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 V\r\n",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芯片的通信序列图,其可分为以下几个步骤:

  1. 发送上电命令:上电命令是0x01
  2. 发送测量命令:以发送的测量命令是“连续高分辨率测量(0x10)”为例,先是“起始信号(ST)”,接着是“器件地址+读写位”,然后是应答位,紧接着就是测量的命令“00010000”,然后应答,最后是“结束信号(SP)”。(相比于OPT3001的写入过程,BH1750少了一个发送寄存器地址的步骤,因为它只有一个寄存器,所以就没必要了)
  3. 等待测量结束:测量的时间手册上有,高分辨率连续测量需要等待的时间最长,手册上面写的是平均120ms,最大值180ms,所以为了保证每次读取到的数据都是最新测量的,程序上面可以延时200ms以上。
  4. 读取数据:先是“起始信号(ST)”,接着是“器件地址+读写位”,然后是应答位,紧接着接收1个字节的数据(单片机在这个时候要把SDA引脚从输出改成输入),然后给BH1750发送应答,继续接收1个字节数据,然后不应答(因为接收的数据只有2个字节,收完就可以结束通讯),最后是“结束信号(SP)”。
  5. 计算结果光照强度 =(寄存器值[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: %d\r\n",Data_H);
//	sprintfU3("DATA_L: %d\r\n",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: %d\r\n",Data_H);
//	sprintfU3("DATA_L: %d\r\n",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 State\r\n");
	}
     for(i=0;i<ALS_Read_Time;i++)
	  {    
			luxVal=BH1750_ReadData();
			luxVal=ALS_k*luxVal+ALS_b;
			delay_ms(ALS_Delay_Time);
			sprintfU4("%d\r\n",luxVal);
			if(luxresult<luxVal) luxresult=luxVal;		
	  }
	  if(h_flag==S_F)
	  {
	//	  sprintfU4("Pass\r\n@_@");
	 // }
	 // else
	  //{
		if(luxresult>=SetLux)
		{
			sprintfU4("S LED On %d Pass\r\n@_@",luxresult);
		}
		else
		{ 
			sprintfU4("S LED Off %d Pass\r\n@_@",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 Pass\r\n@_@");
}	

/***************************************************************************
** 函数名称   :   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 Pass\r\n@_@");
	
	}
	else	
	{
		printfU4("Test LED Fail\r\n@_@");
	}
}
/***************************************************************************
** 函数名称   :   Read_LED
** 功能描述   :  	显示光强数据
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :		
***************************************************************************/
void Read_LED(void)
{
		GetLightVal=0;
	//sprintfU4("Read LED %d Pass\r\n@_@",LuxValue);
	//LuxValue=ALS_k*LuxValue+ALS_b;
	if(ALS_num>5)	sprintfU4("Read Led OK, the value is: %d lux\r\n@_@",LuxValue);
	else sprintfU4("Read Led OK, the value is: %d lux\r\n@_@",ALS_Value[0]);
}
/***************************************************************************
** 函数名称   :   ReadLEDlightValue
** 功能描述   :  	扫描光强
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :		此函数应该放在主函数中的循环中
***************************************************************************/
void ReadLEDlightValue(void)
{
    uint32_t Tling=0;
    if(GetLightVal==1)
    {
		Tling=BH1750_ReadData();
	//	sprintfU4(": %d lux\r\n@_@",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] = %d\r\n",i,ALS_Value[i]);
		}
	}
	else printfU4("All BH1750 Data =0\r\n");	
	printfU4("Read BH1750 Data Pass\r\n@_@");	
}
/***************************************************************************
** 函数名称   :   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 Pass\r\n@_@");
}

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,表示已达满量程状态。

在这里插入图片描述


  1. 芯片初始化与参数定义
#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 小电阻与大电阻测量方法

一般小电阻采用恒流源测试精度更高; 大电阻采用分压电路 + 分挡位测试精度更高、范围更大;

在这里插入图片描述

  1. 小电阻恒流源方法测试代码
#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
{
	uint32_t temp = 0;
	float temp3 = 0;
	float temp2 = 0;
    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(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:
			{
					temp = AD7780_Read();
					if(temp< 0x7FFFFF) // AD读数为负值
					{
						temp3 = 0.0-(0x800000-temp);
						ResValue = 1000.0*Res_mVValue*temp3/(8388607.0)/Res_mAValue;
					}
//					temp2 = temp&0x7FFFFF;
//					temp2 =  1000.0*Res_mVValue*temp2;
//					temp2 =  temp2/(8388607.0);
//					temp2 = temp2/Res_mAValue;
					else ResValue = 1000.0*Res_mVValue*(AD7780_Read()&0x7FFFFF)/(8388607.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;
    }
}
  1. 大电阻分挡分压方法测试代码
__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芯片CS1280应用

- 宏定义
// 输出速率
#define SPEED10		0
#define	SPEED40		1
#define SPEED640	2
#define	SPEED1280	3
// PGA 选择
#define PGA1		0
#define PGA2		1
#define PGA64		2
#define PGA128		3
// 通道
#define CH_A		0	//通道A
#define CH_B		1	//通道B
#define CH_T		2	//温度
#define	CH_S		3	//内短
// REF 输出开关
#define REFO_OFF	1
#define REFO_ON		0
// 命令字
#define READ_COM	0x56	//读配置寄存器
#define	WRITE_COM	0x65	//写配置寄存器

// CLK 和 DAT IO 配置
#define CS1238_PORT_CLK			GPIOD
#define CS1238_CLK				GPIO_Pin_5
#define CS1238_PORT_DAT			GPIOD
#define CS1238_DAT				GPIO_Pin_6

#define Set_CS1238_CLK  	GPIO_SetBits(CS1238_PORT_CLK,		CS1238_CLK)
#define Clr_CS1238_CLK 		GPIO_ResetBits(CS1238_PORT_CLK,		CS1238_CLK) 
#define Set_CS1238_DAT  	GPIO_SetBits(CS1238_PORT_DAT,		CS1238_DAT)
#define Clr_CS1238_DAT  	GPIO_ResetBits(CS1238_PORT_DAT,		CS1238_DAT)
#define READ_CS1238_DAT     GPIO_ReadInputDataBit(CS1238_PORT_DAT,CS1238_DAT)

// IO 电平设定
#define CC_Enable()		PAout(15) = 1
#define CC_Disable()	PAout(15)= 0
#define Gain_Enable()	PCout(12)= 1
#define Gain_Disable()	PCout(12)= 0
#define NTC_DCR_Enable() PAout(12)=1
#define NTC_DCR_Disable() PAout(12)=0

#define DCR_PowerON()		PDout(3)= 0
#define DCR_PowerOFF()		PDout(3)= 1
#define DCR_LowChannal()	PAout(11)= 0;PDout(0)= 0;
#define DCR_MidChannal()	PAout(11)= 1;PDout(0)= 0;
#define DCR_HighChannal()	PAout(11)= 0;PDout(0)= 1;
u32 adc_data[1000]={0};

float NTC_DCR_200R_K = 1;
float NTC_DCR_25K_K = 1;
float NTC_DCR_400K_K = 1;
float NTC_DCR_B = 0;
float resRefValue[3]= {200,24900,402000}; // 不同测试量程的参考电阻
float GNDDCRValue[1000]= {0,0,0};

float GND_DCR_K = 1;
float GND_DCR_B = 0;

float GND_DCR1_K = 1;
float GND_DCR2_K = 1;
float GND_DCR1_B = 0;
float GND_DCR2_B = 0;



//==========================================================================
void Read_NTC_DCR(void)
{
	float res_value; 

	res_value = Read_NTC_Value(10);
	printf("NTC DCR:");	
	str_send(res_value);
	printf(" kohm\r\n");	  
	printf("Read NTC DCR Pass\r\n");
	printf("@_@");
}




//==========================================================================
float Read_NTC_Value(u16 cycle)	
{
	#define FULLRANGE  0x7fffff // 正满量程
	#define UNITS 	   1000	

	#define NTC_DCR		1.0165
	#define AVG		1 // 是否求平均
	
	u32 adc_value;
	float res_value;
	
	CS1238_Config(CH_B,PGA1,SPEED640,REFO_OFF);	// 向CS1238 写入config 寄存器参数
	DCR_PowerON(); // 使能CH444G
	DCR_MidChannal(); // 选择测试量程

	delay_ms(100);	
	adc_value = ReadAdc_CS1238(cycle,AVG); // 读10次并求平均
		
	if(adc_value > FULLRANGE) adc_value = 0; 
	res_value = adc_value * resRefValue[1]/(FULLRANGE-adc_value);	
	res_value = NTC_DCR_25K_K * res_value * NTC_DCR + NTC_DCR_B; // 计入k/b 值
	res_value = res_value > 10000000 ? 10000000 : res_value; // 确保res_value 不超过10000000
	return res_value; //ohm
}






//==========================================================================
void CS1238_Config(u8 ch,u8 Gain,u8 Speed,u8 Vref)
{
	u8 i,config = 0;
	u32 adc_data=0;
	u8 rdata=0;
	
	config=ch+(Gain*4)+(Speed*16)+(Vref*64); // 将四个参数组合成一个8位的配置值。每个参数都乘以一个特定的权重(4、16、64),这样每个参数就能在config字节中占据不同的位
//	printf("config:0x%02x\r\n",config);
	Clr_CS1238_CLK;
	delay_us(10);
	Config_DAT_IN();	//config dat input
	delay_us(10);
	while(!READ_CS1238_DAT);//等待ADC REDY
	while(READ_CS1238_DAT);//等待ADC REDY
	delay_us(10);
	adc_data = 0;
	for(i=0;i<24;i++)	//	第1-24个时钟,读ADC数据
	{
		Set_CS1238_CLK;
		delay_us(10);
		adc_data <<=1;		
		if(READ_CS1238_DAT)	adc_data++;
		delay_us(10);
		Clr_CS1238_CLK;
		delay_us(10);
	}
	Set_CS1238_CLK;	
	delay_us(10);
	Clr_CS1238_CLK;//第25个时钟,
	delay_us(10);
	
	Set_CS1238_CLK;	
	delay_us(10);
	Clr_CS1238_CLK;//第26个时钟,与第25个时钟一起为读取寄存器写操作状态
	delay_us(10);
	
	Set_CS1238_CLK;	//第27个时钟,把CS1238 DOUT为拉高
	delay_us(10);
	Clr_CS1238_CLK;
	delay_us(10);
	
	Set_CS1238_CLK;	//28
	delay_us(10);	
	
	Config_DAT_OUT();	//config dat output
	delay_us(10);	
	Clr_CS1238_CLK;
	delay_us(10);

	Config_DAT_OUT();	//config dat output
	delay_us(10);
	
	Set_CS1238_CLK;	//第29个时钟,切换CA1238 DOUT为输入
	delay_us(10);
	Clr_CS1238_CLK;	
	delay_us(10);


	for(i=1;i<8;i++)	//第30-36个时钟,输入寄存器读/写命令字
	{
		Set_CS1238_CLK;	
		delay_us(10);		//
		if((WRITE_COM<<i)&0x80)	{Set_CS1238_DAT;	}	
		else 					{Clr_CS1238_DAT;	}
		delay_us(10);		
		Clr_CS1238_CLK;
		delay_us(10);

	}
	Set_CS1238_CLK;		//	第37个时钟,切换DOUT方向(根据第30-36个时钟的命令字)
	delay_us(10);
	
	Config_DAT_OUT();	//config dat input
	delay_us(10);
	
	Clr_CS1238_CLK;
	delay_us(10);

	
	for(i=0;i<8;i++)	//第38-45个时钟,输入配置数据(通道、增益、速度、参考电压)
	{
		Set_CS1238_CLK;	
		delay_us(10);
		if((config<<i)&0x80)	{Set_CS1238_DAT;	}
		else 					{Clr_CS1238_DAT;	}

		delay_us(10);	
		Clr_CS1238_CLK;
		delay_us(10);
	}
	Set_CS1238_CLK;		//	第46个时钟
	delay_us(10);
	Clr_CS1238_CLK;	
	delay_us(10);	
	Config_DAT_IN();	//config dat input
	
//	Set_CS1238_DAT;
//	delay_us(250);
//	delay_us(250);
//	delay_us(250);
//	delay_us(250);

	
}




//==========================================================================
u32 ReadAdc_CS1238(u16 cycle,u8 AVG_Flag)
{
	u16 i;
	u32 adc_value = 0;
	for(i=0;i<cycle;i++)
	{    
		 adc_data[i] = Read_CS1238();  	
	}
	if(AVG_Flag)
		adc_value = adc_average(adc_data,cycle);		//求平均值
	return adc_value;	 	
}




//==================================================================
void CS1238_IO_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);     //使能GPIOA,GPIOD,GPIOE的时钟;相或可以取多组IO口

	GPIO_InitStructure.GPIO_Pin = CS1238_DAT;       // AD7780DOUT                      
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;                              //上拉输入,
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(CS1238_PORT_DAT,&GPIO_InitStructure);    
	
	GPIO_InitStructure.GPIO_Pin =   CS1238_CLK ;     //AD7780GAIN
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                              //上拉输入,
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(CS1238_PORT_CLK,&GPIO_InitStructure);
	
	GPIO_ResetBits(CS1238_PORT_CLK,CS1238_CLK); 

//Clr_CS1238_CLK;	Clr_CS1238_DAT;	
//Set_CS1238_DAT;
//Clr_CS1238_DAT;	
//Set_CS1238_CLK;
//Clr_CS1238_CLK;
}



//==================================================================
void Config_DAT_IN(void)  
{
	GPIO_InitTypeDef GPIO_InitStructure; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	   //????????
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 

	GPIO_InitStructure.GPIO_Pin = CS1238_DAT;      //SDA			
	GPIO_Init(CS1238_PORT_DAT, &GPIO_InitStructure);		 
}



//==================================================================
void Config_DAT_OUT(void) 
{
	GPIO_InitTypeDef GPIO_InitStructure;  //??????
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   //???????
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

	GPIO_InitStructure.GPIO_Pin = CS1238_DAT;      //SDA			
	GPIO_Init(CS1238_PORT_DAT, &GPIO_InitStructure);						     
}





//==================================================================
void Read_Config(void)
{
	u8 i,config = 0;
	u32 adc_data=0;
	u8 rdata=0;
	
	Clr_CS1238_CLK;
	delay_us(10);
	Config_DAT_IN();	//config dat input
	delay_us(10);
	while(READ_CS1238_DAT);//等待ADC REDY
	delay_us(10);
	adc_data = 0;
	for(i=0;i<24;i++)	//	第1-24个时钟,读ADC数据
	{
		Set_CS1238_CLK;
		delay_us(10);
		adc_data <<=1;		
		if(READ_CS1238_DAT)	adc_data++;
		delay_us(10);
		Clr_CS1238_CLK;
		delay_us(10);
	}
	Set_CS1238_CLK;	
	delay_us(10);
	Clr_CS1238_CLK;//第25个时钟,
	delay_us(10);
	
	Set_CS1238_CLK;	
	delay_us(10);
	Clr_CS1238_CLK;//第26个时钟,与第25个时钟一起为读取寄存器写操作状态
	delay_us(10);
	
	Set_CS1238_CLK;	//第27个时钟,把CS1238 DOUT为拉高
	delay_us(10);
	Clr_CS1238_CLK;
	delay_us(10);
	
	Set_CS1238_CLK;	//28
	delay_us(10);	
	
	Config_DAT_OUT();	//config dat output
	delay_us(10);	
	Clr_CS1238_CLK;
	delay_us(10);

	Config_DAT_OUT();	//config dat output
	delay_us(10);
	
	Set_CS1238_CLK;	//第29个时钟,切换CA1238 DOUT为输入
	delay_us(10);
	Clr_CS1238_CLK;	
	delay_us(10);


	for(i=1;i<8;i++)	//第30-36个时钟,输入寄存器读/写命令字
	{		
		Set_CS1238_CLK;	
		delay_us(10);		//WRITE_COM
		if((READ_COM<<i)&0x80)	{Set_CS1238_DAT;	}	
		else 					{Clr_CS1238_DAT;	}
		delay_us(10);		
		Clr_CS1238_CLK;
		delay_us(10);

	}
	Set_CS1238_CLK;		//	第37个时钟,切换DOUT方向(根据命令字)
	delay_us(10);
	
	Config_DAT_IN();	//config dat input
	delay_us(10);
	
	Clr_CS1238_CLK;
	delay_us(10);

	
	for(i=0;i<8;i++)	//第38-45个时钟,输入配置数据
	{
		Set_CS1238_CLK;	
		delay_us(10);
		
		rdata <<= 1;
		if(READ_CS1238_DAT)	rdata++;
		delay_us(10);	
		Clr_CS1238_CLK;
		delay_us(10);
	}
	Set_CS1238_CLK;		//	第46个时钟
	delay_us(10);
	Clr_CS1238_CLK;		
//	delay_us(250);
//	delay_us(250);
//	delay_us(250);
//	delay_us(250);
	printf("rdata:0x%02x\r\n",rdata);
	
}




//==================================================================
u32 Read_CS1238(void)
{
	u8 i = 0;
	u32 adc_data=0;
	
	Clr_CS1238_CLK;
	delay_us(10);
	Config_DAT_IN();	//config dat input
	delay_us(10);
	while(!READ_CS1238_DAT);//等待ADC REDY
	while(READ_CS1238_DAT);//等待ADC REDY
	delay_us(10);
	adc_data = 0;
	for(i=0;i<24;i++)	//	第1-24个时钟,读ADC数据
	{
		Set_CS1238_CLK;
		delay_us(10);
		adc_data <<=1;		
		if(READ_CS1238_DAT)	adc_data++;
		delay_us(10);
		Clr_CS1238_CLK;
		delay_us(10);
	}
	Set_CS1238_CLK;	
	delay_us(10);
	Clr_CS1238_CLK;//第25个时钟,
	delay_us(10);
	
	Set_CS1238_CLK;	
	delay_us(10);
	Clr_CS1238_CLK;//第26个时钟,与第25个时钟一起为读取寄存器写操作状态
	delay_us(10);
	
	Set_CS1238_CLK;	//第27个时钟,把CS1238 DOUT为拉高
	delay_us(10);
	Clr_CS1238_CLK;
	delay_us(10);
//	delay_us(250);
//	delay_us(250);
//	delay_us(250);
//	delay_us(250);
	return adc_data;
}




//==================================================================
u32 adc_average(u32 *p,u8 len)
{
		u32 value = 0;
		u32 temp = 0;
		unsigned char	 i,j;
		
		for(j = 0; j < len - 1; j++)
		{
				for(i = 0; i < len - 1 - j; i++)
				if(p[i] > p[i + 1])
				{
						temp = p[i];
						p[i] = p[i + 1];
						p[i + 1] = temp;
				}
		}
		for(i = 3;i < len - 2; i++ )
			value += p[i];
		value /= len-3-2;
		return (u32)value;
}

1.* 解决ADC 分辨率不够导致测试进度不够的解决方案

在软件中对ADC 进行多次采集,多个数据求平均值,可解决ADC 分辨率不够导致测试进度不够的问题;

2. 存储


2.1 EEPROM芯片 - AT24Cxx

AT24Cxx常用的IIC通讯的EEPROM 器件;
数据存储根据地址高位在前低位在后的原则;

如使用芯片为AT24C02,最低地址位为0,最高地址位为255,地址为255的数据为0x77,地址为254的数据为0x86,则从地址255 连续读取两个字节时,读到的数据为0x7786;

❗❗❗注意在对芯片进行写入时,为避免数据错位覆盖,应提前规划好写入地址与写入的数据字节长度;

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.2 代码应用例程

代码备份位置:百度网盘 - 知识资源 - 电子工程 - 单片机 - AT24Cxx 源代码.rar

  1. AT24CXX_WriteOneByte();AT24CXX_ReadOneByte();函数的应用例程,在AT24CXX 指定地址读写1个字节数据(最大值为0xFF)
		u8 write_data = 255;// 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u8 read_data = 0x00;
		
		delay_ms(1000);
		AT24CXX_WriteOneByte(start_address,write_data,i2c_Channel);
		delay_ms(1000);
		read_data = AT24CXX_ReadOneByte(start_address,i2c_Channel);
		delay_ms(1000);
    // 验证读取的数据是否与写入的数据一致  
    if (read_data == write_data) {  
        printf("Data read and written are the same.\n");  
    } else {  
        printf("Data read and written are different!\n");  
    } 
		Uart4_Printf("address 0x%04X: 0x%02X\n", start_address, read_data);
  1. AT24CXX_WriteLenByte();AT24CXX_ReadLenByte();函数的应用例程,在AT24CXX 指定地址读写长度为Len 个字节的数据(最大值为0xFFFF FFFF)
		u32 write_data = 0xFFFFFFFF;// 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u32 read_data = 0x00;
		
		delay_ms(1000);
		AT24CXX_WriteLenByte(start_address,write_data,4,i2c_Channel);
		delay_ms(1000);
		read_data = AT24CXX_ReadLenByte(start_address,4,i2c_Channel);
		delay_ms(1000);
    // 验证读取的数据是否与写入的数据一致  
    // 验证数据  
    if (read_data == write_data) 
		{  
        Uart4_Printf("Data written and read are same.\n");  
    } 
		else 
		{  
        Uart4_Printf("Data written and read are different.\n");  
    } 
		Uart4_Printf("Written: 0x%08X, Read: 0x%08X\n", write_data, read_data);
  1. AT24CXX_Write();AT24CXX_Read();函数的应用例程,功能与上面AT24CXX_WriteLenByte() 相同,只是传入参数改为指针类型,多用于数组
		int i = 0;
		u8 data_to_write[] = {0x01, 0x02, 0x03, 0x04}; // 要写入的数据  
		u16 start_address = 0x0000; // 起始地址
		u8 read_buffer[sizeof(data_to_write)]; // 读取数据的缓冲区
		u8 i2cChannel = 0x01; // IIC通道或设备地址 
		
		AT24CXX_Write(start_address, data_to_write,sizeof(data_to_write),i2cChannel);	
		delay_ms(1000);
		delay_ms(1000);
		AT24CXX_Read(start_address,read_buffer,sizeof(read_buffer),i2cChannel);
		delay_ms(1000);
		delay_ms(1000);
		
    // 验证读取的数据是否与写入的数据一致  
    if (memcmp(data_to_write, read_buffer, sizeof(data_to_write)) == 0) 
    {  
        Uart4_Printf("Data read and written are the same.\n");  
    } else {  
        Uart4_Printf("Data read and written are different!\n");  
    }  
  
    // 输出读取的数据 
    Uart4_Printf("Read data: ");  
    for (i = 0; i < sizeof(read_buffer); i++) 
		{  
        Uart4_Printf("0x%02X ", read_buffer[i]);  
    }  
    Uart4_Printf("\n");
	delay_ms(1000);
	delay_ms(1000);
  1. AT24CXX_WriteFloat();AT24CXX_ReadFloat();函数的应用例程,在AT24CXX 指定地址开始读写float 型数据
		float dataToWrite = 3.14159f; // 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
    u8 bufferWrite[4];  
    u8 bufferRead[4];
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		float dataRead;
    // 将float转换为字节数组  
    memcpy(bufferWrite, &dataToWrite, 4);
		
		delay_ms(1000);
		AT24CXX_WriteFloat(start_address, bufferWrite, i2c_Channel);  
		delay_ms(1000);
		AT24CXX_ReadFloat(start_address, bufferRead,i2c_Channel); 
		delay_ms(1000);
    // 将字节数组转换回float并验证  
    memcpy(&dataRead, bufferRead, 4); 
    // 验证数据是否一致  
    if(dataRead == dataToWrite)  
    {  
        Uart4_Printf("The read float is the same as the written float.\n");  
    }  
    else  
    {  
        Uart4_Printf("The read float is NOT the same as the written float.\n");   
    }
		Uart4_Printf("Original: %f, Read: %f\n", dataToWrite, dataRead); 
  1. AT24CXX_Write_double();AT24CXX_Read_double();函数的应用例程,在AT24CXX 指定地址开始读写double 型数据
		double dataToWrite = 3.14159265358979323846; // 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		double dataRead = 0.0;
		
		delay_ms(1000);
		AT24CXX_Write_double(start_address, dataToWrite, i2c_Channel);  
		delay_ms(1000);
		dataRead = AT24CXX_Read_double(start_address, i2c_Channel); 
		delay_ms(1000);
    // 比较两个double值是否相等(由于浮点数的精度问题,不能直接比较)  
    if (dataRead == dataToWrite) {  
        Uart4_Printf("Data written and read are same.\n");  
    } else {  
        Uart4_Printf("Data written and read are different.\n");  
    }
		Uart4_Printf("Written: %f, Read: %f\n", dataToWrite, dataRead);
  1. 读取EEPROM 内所有数据
		// 读取EEPROM 内所有数据
		u8 i=0;
		u16 addr = 0; // 地址从0开始
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u8 dataRead;
		
		for(i=0;i<=EE_TYPE;i++) // EE_TYPE 为当前硬件AT24Cxx 的容量
		{
			Uart4_Printf("\r\n");
			delay_ms(10);
			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
			Uart4_Printf("addr%d - %d",i,dataRead);
		}
  1. 排查EEPROM 的读写功能
		u16 i=0;
		u16 addr = 0;
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u8 dataRead;
		
		// 全写入1
		for(i=0;i<=EE_TYPE;i++) // EE_TYPE 为当前硬件AT24Cxx 的容量
		{
			AT24CXX_WriteOneByte(addr+i,0xff,i2c_Channel);
			if(i==EE_TYPE){break;}
		}
//		// 全读取在串口查看
//		for(i=0;i<=EE_TYPE;i++)
//		{
//			Uart4_Printf("\r\n");
//			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
//			Uart4_Printf("addr%d - %d",i,dataRead);
//			if(i==EE_TYPE){break;}
//		}
		// 自动对比,有错误就报错
		for(i=0;i<=EE_TYPE;i++)
		{
			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
			if (dataRead != 0xff){Uart4_Printf("Error:addr%d - %d",i,dataRead);}
			if(i==EE_TYPE){break;}
		}
		Uart4_Printf("\r\n---------------------------------Write complete---------------------------------------------");
		// 全写入0
		for(i=0;i<=EE_TYPE;i++)
		{
			AT24CXX_WriteOneByte(addr+i,0x00,i2c_Channel);
			if(i==EE_TYPE){break;}
		}
//		// 全读取在串口查看
//		for(i=0;i<=EE_TYPE;i++)
//		{
//			Uart4_Printf("\r\n");
//			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
//			Uart4_Printf("addr%d - %d",i,dataRead);
//			if(i==EE_TYPE){break;}
//		}
		// 自动对比,有错误就报错
		for(i=0;i<=EE_TYPE;i++)
		{
			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
			if (dataRead != 0x00){Uart4_Printf("Error:addr%d - %d",i,dataRead);}
			if(i==EE_TYPE){break;}
		}
		Uart4_Printf("\r\n----------------------------Read complete--------------------------------------------------");

2.3 延长EEPROM寿命

对于Flash或EEPROM,读操作对其寿命影响不大,写操作(对Nand Flash则为块擦除)次数基本决定了存储器寿命;而且写入寿命在每个位之间是独立的。延长EEPROM寿命有以下几种方法:

  1. 不固定数据存放的地址,而是用一个固定的基地址加上EEPROM内的一个单元的内容(即偏移地址)作为真正的地址;若发现存储单元已坏(写入和读出的内容不同),则偏移地址加1,重新写入。此方法有一弊端:当某一个EEPROM单元被写坏再用下一个单元时,后面到所有数据都会被覆盖。
  2. 从第一个存储单元开始存储数据N次,然后转到下一个单元再存N次,依次类推,当最后一个单元存放N次之后,再转到第一个单元重新开始。即不重复读写(擦写)某几个存储单元,尽量用到EEPROM上的所有存储单元,防止某几个存储单元反复擦写导致损坏。
  3. 对于一些需要上电初始化的数据,在每次写入EEPROM时,附带写入一个标志位。AT24CXX_WriteOneByte(DATA_FLAG_Addr,0x0A);,上电初始化EEPROM时,读取该位并进行比较,查看EEPROM是否被写入过;
temp = AT24CXX_ReadOneByte(DATA_FLAG_Addr);
if(temp==0x0A)
{
	// 读取EEPROM中的数据到对应变量中
}
else
{
	// 第一次上电,向EEPROM 写入一些数据的初始值
	...
	AT24CXX_WriteOneByte(DATA_FLAG_Addr,0x0A); // 写入标志位,表示该系统已写入过一些初始化数据
}

2.4 关于存储器读写地址

存储器写入首先不能超过存储器的最高数据位,如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响应中断后,转去执行的相应处理程序

  • 中断的特点
    1. 同步工作:中断是CPU 和接口之间的信息传递方式之一,它使CPU 与外设同步工作,较好地解决了快速CPU 与慢速外设之间的匹配问题;
    2. 异常处理:针对难以预料的异常情况,如掉电、存储出错、运算溢出等,可以通过中断系统由故障源向CPU 发出中断请求,再由CPU 转到相应的故障处理程序进行处理;
    3. 实时处理: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 初始化的同时配置好,中途不能对其进行修改,以免造成中断混乱;


  • 抢占优先级和响应优先级的区别
    • 抢占 = 打断别人,高优先级的抢占优先级可以打断正在进行的低抢占优先级中断(值越小说明级越高);
    • 响应 = 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;
      在这里插入图片描述
  • 若需要挂起/解挂中断,查看中断当前激活状态,分别调用相关函数;

应用举例:

  1. 假设MCU 设置中断优先级组为2,然后对以下三个中断进行配置:
  2. 中断4(FLASH中断 )抢占优先级为2,响应优先级为1
  3. 中断5(RCC中断)抢占优先级为3,响应优先级为0
  4. 中断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. 产生中断:如下图,红色线路1-2-4-5是产生中断的过程
    2. 产生事件:如下图,绿色线路1-2-3-6-7-8是产生事件的过程,其中标黄的 20/ 代表着有20条相同的线路
  • EXTI功能框图:
    EXTI功能框图


关于EXTI 的代码都在固件库的 stm32f10x_exti.hstm32f10x_exti.c 文件中;

STM32 的每个 IO 都可以作为外部中断的中断输入口,比如STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断/事件都有独立的触发和屏蔽设置。

其中,STM32F103 的19 个外部中断为:

  1. 线 0~15:对应外部 IO 口的输入中断。
  2. 线 16:连接到 PVD 输出。
  3. 线 17:连接到 RTC 闹钟事件。
  4. 线 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个寄存器:
在这里插入图片描述

  1. SCB_AIRCR:32 位寄存器,有效位为第8到10位,用于设置5种中断优先级分组;
  2. IP240个8位寄存器,每个中断使用一个寄存器来确定优先级,每个8位寄存器有效位为第4到7位,用于设置抢占优先级与响应优先级;

如STM32F10x系列一共60个可屏蔽中断,那么它就只使用了IP[59]~IP[0l;

在这里插入图片描述

  1. ISER:8个32位寄存器,每个位控制一个中断的使能,写1使能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ISER[0]和ISER[1]; ISER[0]的bito~ bit31分别对应中断0~ 31、ISER[1]的bit0~ bit27对应中断32~ 59;

  1. ICER:8个32位寄存器,每个位控制一个中断的失能,写1失能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ICER[0]和ICER[1]; ICER[0]的bito~ bit31分别对应中断0~ 31、ICER[1]的bit0~ bit27对应中断32~ 59;

  1. ISPR:8个32位寄存器,每个位控制一个中断的挂起,写1失能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ISPR[0]和ISPR[1]; ISPR[0]的bito~ bit31分别对应中断0~ 31、ISPR[1]的bit0~ bit27对应中断32~ 59;

  1. ICPR:8个32位寄存器,每个位控制一个中断的解挂,写1失能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ICPR[0]和ICPR[1]; ICPR[0]的bito~ bit31分别对应中断0~ 31、ICPR[1]的bit0~ bit27对应中断32~ 59;

  1. 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:
  1. 输入配置为浮空输入
  2. 若涉及到端口复用问题,还需要打开相应的端口复用时钟
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已被触发\r\n");
	EXTI_ClearITPendingBit(EXTI_Line9); //清除 LINE9 上的中断标志位
}	

3.5 其他:

  1. 基于CORTEX-M3内核的硬件因素,清除中断标志不会马上生效,需要一段时间,如果你的中断服务程序时间很短,就会出现中断重复进入的异常;这种情况,可以在程序中增加去抖动和延时功能;

参考:STM32 官方参考手册;

  • 18
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Truffle7电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值