STM32的ADC配置问题

问题介绍

最近要使用STM32F103C8T6来做个数字万用表,于是开始学习STM32,要用到32内部的12位ADC
等于是刚刚接触STM32,一切从零开始,现在分享下如何简单的使用ADC


预备知识

RCC:
这个是用来设置时钟的,比如我可以设置我的系统时钟频率等
TIM:
顾名思义,是timer的缩写,是定时计数器.
RCC 和 TIM的区别:
RCC用来设置我32的系统时钟频率或者是一些其他硬件的时钟频率
而TIM是在某个时钟频率下工作的一个计数器,这个频率可以来自RCC的设置,也可以来自外部
注意,RCC设置频率的来源也可以是外部或者内部(内部不准确,我们一般不用,这也是为什么要外接8MHz晶振的原因),而后产生一个内部时钟频率送给TIM

为什么要说时钟呢?因为我要使ADC的采样率达到最大,也就是1MHz的采样率,而达到这样的采样率就需要设置ADC的时钟频率,ADC最大时钟频率是14MHz。这两者什么关系呢?
后面介绍,总之先知道要达到最大的采样率就需要设置我们的时钟


以下正文

那么就让我开始来配置RCC吧!
No.1
首先我们应该用外部接的8MHz晶振来做时钟源。外部高速晶振:HSE;内部高速晶振:HSI
void RCC_HSEConfig(u32 RCC_HSE) 这个函数来启动,内部参数设置 RCC_HSE_ON

RCC_HSEConfig(RCC_HSE_ON);//开启8MHz外部晶振

然后检测外部高速晶振是否正常启动
RCC_WaitForHSEStartUp(); 这个函数, 这个函数返回 SUCCESS 这个参数则代表正常启动

ErrorStatus HSEStartUpStatus;//设置标志位
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)//若外部晶振正常启动
{
/* Add here PLL ans system clock config */
}

这时候我们要通过PLL锁相环来使 外部接的晶振作为输入,输出另一个稳定频率的时钟信号
即我们要用PLL来进行倍频
这里我们设置PLL输出 = 8MHz * 7 = 56MHz (那就是要进行7倍频)

//RCC_PLLSource_HSE_Div1 意思是 PLL的输入时钟 = HSE时钟频率
//RCC_PLLMul_7 表示 7倍频
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7);

然后输出

RCC_PLLCmd(ENABLE);//PLL输出使能
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);//等待PLL输出

到目前为止我们得到一个56MHz的时钟频率


No.2
然后我们要利用PLL输出的这个频率作为我们STM32的系统时钟

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//设置系统时钟为56MHz
while(0x08 != RCC_GetSYSCLKSource());//等待系统时钟被正确设置

这样系统时钟就被我们设置好了
接下来的任务就是要设置AHB时钟了
what?AHB时钟又是个啥?
AHB(Advanced High performance Bus)高级高性能总线 或者我们叫它 系统总线
就是我们要设置STM32内部总线的时钟频率
而且AHB又有高低速之分,也就是说我们要设置两个时钟分别给高速AHB低速AHB
AHB掌管着DMA时钟,SRAM时钟和FLITF时钟,它并不直接管ADC的时钟,那我们为什么还要设置它呢?先别急,往下看

RCC_HCLKConfig(RCC_SYSCLK_Div1);//设置AHB时钟(HCLK)
RCC_PCLK2Config(RCC_HCLK_Div1);//设置高速AHB时钟 PLCK2为56MHz (最大72MHz)
RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速AHB时钟 PLCK1为28MHz (最大36MHz)
//注:这里面Div1表示一倍分频,也就是不分频。 PLCK2 = HCLK = 56MHz
//Div2表示2倍分频 PLCK1 = HCLK / 2 = 28MHz

总线设置好了我们就终于可以开始设置ADC的时钟频率了
但这里又要提到一个名词APB
APB(Advanced Peripheral Bus)外围总线
这个才是直接管ADC时钟的总线,APB又分APB1APB2
APB1管TIMx (x = 2, 3, 4 ……) WWDG,SPI2, USART2, USAT3, I2C, CAN的时钟
APB2管TIM1, GPIOx, ADC1, ADC2, SPI1, USART1的时钟
很明显我们只需要对APB2的ADC功能进行设置就行

//使能ADC & GPIOA
//这里我用ADC1采样,PA0端口,具体看各个芯片的数据手册
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);

重点来了

现在来设置ADC了!!!
来上函数 void ADC_ADCCLKConfig(u32 RCC_ADCCLKSource)
看看这个参数 RCC_ADCCLKSource: 定义ADCCLK,该时钟源自APB2时钟(PCLK2)
懂了吧,为啥非要对AHB进行设置,ADC的时钟来源于AHBPLCK2

//RCC_PCLK2_Div4 意思是 ADC时钟 = PCLK2 / 4 = 14MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div4);//ADC最大时钟频率是14MHz

至此,我们就对ADC时钟设置完了,一般来说,我们的STM32系统时钟都是设置的是72MHz,但这里我们为什么非要费那么老劲来设置RCC呢?还是上面这个函数,它的参数一共就4个
Div2 Div4 Div6 Div8 也就是2 4 6 8 分频。
72MHz并不能通过这四个分频得到14MHz最大时钟,所以我们特地设置56MHz,通过4分频,产生14MHz的ADC最大时钟频率。72MHz最大能做到 72 / 6 = 12MHz

来吧,上一份完整的RCC代码

static void RCC_ConfigInitail()
{ErrorStatus HSEStartUpStatus;FlagStatus Status;//RCC配置RCC_DeInit();//重置RCC_HSEConfig(RCC_HSE_ON);//外部8MHz晶振启动!HSEStartUpStatus = RCC_WaitForHSEStartUp();if(SUCCESS == HSEStartUpStatus)//若启动成功{RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7);//56MHz PLL输出RCC_PLLCmd(ENABLE);//PLL输出使能while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);//等待PLL输出成功    //设置系统时钟56MHzRCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);while(0x08 != RCC_GetSYSCLKSource());//等待设置成功       RCC_HCLKConfig(RCC_SYSCLK_Div1);RCC_PCLK2Config(RCC_HCLK_Div1);//PLCK2 56MHzRCC_PCLK1Config(RCC_HCLK_Div2);//PLCK1 28MHz //使能APB2外设时钟 ADC & GPIOARCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div4);//ADC1时钟频率 14MHz}
}

等有时间再更新后面的
2018 / 4 / 4 更新……

我是分割线


好了,我们现在来说一说ADC的配置吧
ADC的配置就没什么值得说的,它没有定时器难理解
直接上代码,注释就是解释

ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;//GPIO配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//GPIO采用模拟输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//对PA0初始化
//因为我使用的是ADC1_IN0就是通道0,而这对应STM32C8T6的PA0口
//对于其它型号的要具体看芯片手册//ADC配置
ADC_DeInit(ADC1);//重置ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC1和ADC2单独工作,互不影响
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//ADC单次采样,即采样一次就停止
ADC_InitStructure.ADC_ScanConvMode = DISABLE;  //ADC单通道采样(ENABLE是多通道扫描)
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发ADC
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1;//ADC通道转换数目,我们只用一个ADC,那么就是1
//对于多通道采集才使这个值 >= 2, 取值范围是1~16ADC_Init(ADC1,&ADC_InitStructure);//初始化
ADC_Cmd(ADC1, ENABLE);//使能//ADC校准
ADC_ResetCalibration(ADC1);//重置ADC校准器
while(ADC_GetResetCalibrationStatus(ADC1));//等待重置结束ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1));//等待校准完成ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5);

最后一句话是重点
这句话是重点,看函数名就知道是对ADC通道配置的
这里我们就用一个通道,所以就只写一句,如果有多通道,那么就多写几句,参数变一下即可
要知道ADC转换周期是12.5个ADC时钟周期 而ADC_SampleTime_1Cycles5就是ADC采样时间为
1.5个周期,所以一共12.5 + 1.5 = 14个周期
14MHz / 14 = 1MHz,这样我们就能得到1MHz最大的采样率了
除了1.5以为,STM固件库还给出了其它的一些稀奇古怪的周期
例如:
7.5
13.5
28.5
41.5
55.5
71.5
239.5

呐,就这,ADC最基本的应用就配置完成了
剩下的就是些不痛不痒的代码了

static u16 Get_ADC_Value()
{ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件启动,ADC开始转换while(ADC_GetSoftwareStartConvStatus(ADC1));//等待转换完成return ADC_GetConversionValue(ADC1);//返回得到的ADC值
}

到目前为止ADC的最简单的配置就完成了
下面是我个人的情况,可以选择性阅读
下面是我实力分析为什么在高采样率的情况下,上面这些普通的代码不适合测AC交流


这么说吧,这是我一开始写的代码,目的是为了采集交流信号(这不废话么)
要求是测得10HZ ~ 100KHZ的交流信号的有效值
于是我想采用计算真.有效值的方法进行计算
真.有效值的计算公式是
sqrt( 1/T * ∫ f(x)² dt ) (积分区间是0 ~ T)
将之变成离散的信号处理,那么公式就又积分变到求和
sqrt( 1/n * ∑ f(x)² )(求和的话,那就是从0 ~ n)

那么也就是说我需要采样,一个周期采样n个点,n越多,计算得到的真有效值就越接近真值,跟做个示波器差不多了

如此一来我写了如下的程序

//times表示一个周期采样的个数,即上面说的n
float Get_ADC_EffectiveValue(u8 times)
{float sum = 0.0;//∑ f(x)² float temp = 0.0;//得到返回的ADC值u8 t = 0;for(t = 0; t < times; t++){//12位采样(0xFFF = 4096),STM32是3.3V供电//所以 3.3 / 0xFFF ≈ 0.000805664temp = (Get_ADC_Value() - 2048) * 0.000805664f;sum += temp * temp;}return (float)sqrt(sum / times);
}

这里我是通过电路将AC信号控制在0 ~ 3.3V,也就是原本的AC信号加上一个DC直流偏置
直流偏置 = VCC / 2,所以我要Get_ADC_Value()之后减去直流偏置,得到原来真实的量

这里再补充一个小细节,再0.000805664后加上个f,意思是告诉CPU这是一个float型常量,别给我弄成个double型了,因为STM32是32位CPU,那么也就是说在32位以下的数据计算速度都差不多。
float占4个字节(32位),double占8个字节(64位),很明显处理float要比double快多了,以后建议能用float就尽量用float

那么好了,我的具体方案是这样的,通过TIM的输入捕获功能来捕捉到信号的上升沿,在通过计算差值得到周期T,我一共捕捉11个上升沿,这样我就得到了10个周期,求平均值后使误差减小
然后ADC开始采样,根据得到的周期来确定一个周期我要采样几个点,就这样
然而后面我发现一系列问题,导致我现在否定了这个方案。

来,我们来分析分析

while(1)
{反复分析;//2333333
}

首先我们的定时计数器是工作在56MHz下的,我设置的可计数值为65535,不分频
那么有可能这个周期比较长,我计数器在从0计数到65535后,还没捕捉到第二个上升沿,那么我的这个计数器就会溢出。不要紧,我设置一个全局变量u8 OverFlow = 0;//记录溢出次数
每当我的计数器溢出的话,OverFlow ++;

我计数的Counter = f_TIM / f_IN
f_TIM表示计数器频率即56MHz,f_IN表示输入信号的频率
假如输入信号频率f_IN = 100KHz,那么Counter = 560,嗯,没什么问题
假如f_IN = 10Hz,那么Counter = 5.6 * 1e6,这个可以么?不会太大么?
OverFlow 最大计数255,所以最大Counter = 255 * 65536,这么看有点看不出来,不是很直观
反过来求一下5.6 * 1e6 / 65536 后向下取整为85,也就是说OverFlow 最大只会计数到85,也不是问题
也有人会问,中断不管么?有时间误差啊!
管什么?最多85 + 1次中断,+1是因为捕获到上升沿。急什么,忽略这个时间误差

假如我只知道f_IN,我想知道我的f_TIM应该设置多少?
这样,我们假设Counter >= 100, 那么我计数器从0计数到1的时间间隔t = T_IN / Counter
也就是最小误差(仪器误差)t <= T_IN /100,嗯,1%不到的误差,可以忽略了。
结论一:即当Counter >= 100 时,可以忽略计算周期的误差
那么我就取Counter >= 100
所以f_TIM >= 100 * f_IN,STM32最大72M时钟频率,所以理论上能精确计算的最大输入信号周期的为720KHz。当然,这不是绝对的,你可以通过输入捕获预分频,来扩大这个值,误差还是 1 / Counter这里我们不做过多讨论

由于我们的Counter最小为560,所以根据结论一 可知,一个周期下,输入捕获计算周期的误差忽略,再加上我还通过取平均来减小误差,使得计算得到的误差更小了
结论二:输入捕获计算周期的误差可以忽略不计,不需要管输入捕获了

接下来我们来分析ADC
(嘿嘿嘿!好玩的来了!)

我们得到了输入信号的周期T后,就可以计算一个周期下,ADC采样的次数了
设一个周期内,ADC采样次数为n
则有n = f_ADC / f_IN
f_ADC为ADC的采样频率,f_IN为信号输入频率
那么我们还采用刚才的思路看看,已知f_IN,取n >= 100
则 f_ADC >= 100 * f_IN
确实,前面说过,n越大,越接近积分得到的结果
但是!!!CPU计算也需要时间啊
看看这段代码

 for(t = 0; t < times; t++)// 2n{temp = (Get_ADC_Value() - 2048) * 0.000805664f;// 3nsum += temp * temp;// 3n}

这时候我们就不得不计算程序的时间复杂度了
我就假设+ - * / 都是一样的计算时间,都是一个执行周期(真正的情况下乘除要比加减慢,更不要说你根本不知道编译器翻译成汇编之后会有多少条指令,我这么算算是少的)
t < times 需要n次判断
t++需要n次计算
我就假设Get_ADC_Value()得到值后存入内存不要时间
Get_ADC_Value() - 2048需要n次计算

  • 0.000805664f需要n次计算
    赋值到temp需要n次计算
    temp * temp需要n次计算
    sum + (temp * temp)需要n次计算
    赋值到sum需要n次计算
    呵呵,一共是8n的时间复杂度

ARM3官方给出的平均计算速度是1.25MIPS/MHz
意思是1MHz频率下,每秒执行1.25M条指令。我们是56MHz主频,那么就是每秒56M * 1.25条指令
执行一条指令需要 1 / (56 * 1.25 * 1e6)
也就是说光是用在计算上的时间就需要
t = 8n / (56 * 1.25 * 1e6) ≈ 0.114n (单位us)
我们来看看n取100时候的情况
t = 11.4us, 而对于100KHz信号来说,100KHz = 10us,呵呵,光计算的时间就差了一个周期!!
对于10KHz的信号也会有10%的误差
只有1KHz以下的信号才不会收到影响…………
连采集到的信号都不对,谈什么求和逼近积分……做梦吧,梦里啥都有……
结论三:普通方法不能求得1KHz以上信号的准确 真有效值
正所谓鱼和熊掌不可兼得……

但天无绝人之路,我还有DMA!!!意不意外,惊不惊喜!没想到吧!(此处自动脑补表情包)

可是我现在还不会……等我学会了再回来更新,就酱,如果本文对你有收获,想收藏就收藏,想点赞就点赞
未经允许,不得转载,谢谢
未经允许,不得转载,谢谢
未经允许,不得转载,谢谢
重要的事情说三遍!!!


2022 / 5 / 24 (突然 )更新……

(看我自己以前写的文章,感觉自己多少有点犯病,继续犯病ing)

前言

  • 关于上文提到的测有效值的讨论,真有效值计算起来多少有些麻烦,但是利用ADC+DMA还是可以做到的,但是我也没再管过。最多用到AD637这块芯片测有效值,虽然不是真有效值,但应付毕设,课设还行,把复杂的事交给专业的吧,开摆!
  • 最近重新捡起STM32,但是没有再用过keil了,而是用起了STM32CubeIDE,利用stm32cubeMX生成基础代码,省去基础配置时间,专心开发应用就行。而且也没再使用过std库了,HAL库确实很香。
  • 下面介绍ADC连续采样+DMA双缓冲,实现DAC生成正弦波,ADC采样,并将波形回传至PC并绘制出来。其中DAC也使用了DMA功能,本篇不介绍(挖坑 )
  • 硬件开发平台STM32F407ZET6开发板,时钟频率168MHz,APB1 Timer CLK:84MHz,APB2 Timer CLK:168MHz

预备知识

如果你没有接触过HAL库,那么下面有些词可能听的云里雾里的,这里做一个简单的说明

  • 回调函数:对于一个函数A,我将函数B的函数指针(这个指针指向函数)做为参数传入到函数A中,那么我们称函数B是一个回调函数(Call Back Function)。这样我可以设置不同的回调函数B1,B2,B3……这意味着函数A对不同的函数Bx做出了相同的操作,因此才将其封装起来。当我需要的时候,我只需要传入不同的参数B1,B2,B3……就可以完成不同的操作。在HAL库中,我们往往要编写中断回调函数。当发生中断时,库函数会帮我们判断发生了什么中断,并完成清除标志位等操作,然后调用不同的中断回调函数(HAL_xxxCallback(…)),这些回调函数都是__weak函数,等待用户编写内容。
  • __weak函数(弱函数):__weak关键字用来修饰函数或者变量,表示修饰的函数或变量的使用优先级较低。如果我们定义了一个函数或变量,它与关键字__weak修饰的函数或者变量名一致,则优先使用我们定义的,而忽略__weak修饰的。举个例子:
__weak int FuncCallback(int a, int b)
{//do nothingreturn 0;
}//如果我们不重新定义这个函数,那么编译器就会默认使用上面的那个__weak函数
//但如果我们重新定义了FuncCallback这个函数,那么编译器就会使用我们的
int FuncCallback(int a, int b)
{//user codereturn a + b;
}void UserFunc(void)
{int a = 2, b = 3;int retval;retval = FuncCallback(a, b);//2+3=5
}

DMA简介

DMA的传输数据不占用CPU,是外设(例如ADC或者DAC)到内存、内存到外设、内存到内存的一种传输数据手段。以ADC为例,当ADC转换完成后,由ADC向CPU发出DMA请求,CPU将总线权交给DMA,由DMA控制器将数据搬运到内存中,此时CPU可以干其它事情。等到DMA将一组数据搬运完成之后,我们就可以在中断函数里对数据做处理。
假如我们要计算真有效值,我们可以使用DMA先传输一组数据X1,然后CPU对数据X1做运算的同时,DMA开始接收下一组数据X2,等到X2接收完毕,CPU就可以对X2做处理。

DMA双缓冲

所谓的DMA双缓冲,即一个外设对应两个内存地址,记A和B两个内存地址,当从外设传输完一组数据到A内存后,CPU可以对此内存进行操作,同时开启外设到B内存的数据传输,反过来从内存到外设也是一样的,只是传输数据方向不同。这样CPU对内存的操作和DMA传输数据的操作就可以同时进行。
但是基于HAL库的cubeMX生成的代码对DMA双缓冲的支持并不好,相关的回调函数更是没有定义,用户需要自己设置。但这并不意味着没法实现DMA双缓冲,原因就在于HAL库有大量的HAL_xxxHalfCpltCallback()函数(传输一半回调函数——Half Complete Call Back)
如果我们使用第一种方法,那么我们需要定义两个数组A[10], B[10],假设长度都是10,那么也就是说,每当DMA传输10个数据到A或者B后,DMA会产生一个中断,CPU就可以处理这10个数据了。假如我们使用第二种方法,我们只需要定义数组C[20],那么当DMA传输完成一半的时候,也会有个中断,我们同样可以数据处理,开销是一样的。

ADC & DMA配置

  • ADC单通道连续采样
  • 12bits,数据右对齐
  • 由软件触发(还可以设置为某些TIM的捕获/比较事件)
  • DMA传输方向:由外设到内存,内存地址自动递增
  • DMA模式:循环模式。即传输完一组数据后,内存地址重置,从头开始,覆盖原来的数据
  • DMA传输数据宽度:半字(Half Word = 2 Byte = 16 bit)
  • 采用DMA双缓冲,DMA一边传输数据,CPU一边将数据通过串口发送到PC,由PC绘制波形

具体配置如下图

DMA设置如下(使用DMA的时候,cubeMX强制开启对应中断,如有必要,用户在NVIC中配置优先级即可)

代码配置如下

//File adc.c
……
extern uint16_t ADC_RX_BUF[100];//以100个数据为一组
/* USER CODE BEGIN 1 *///DMA半传输完成回调函数
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{uint8_t i;if(hadc->Instance == ADC1){for(i = 0; i < 50; i++)//处理一半数据{printf("$%.5f;\n", 3.3 * ADC_RX_BUF[i] / 4095);//发送数据到pc,上位机将数据绘制出来,波特兰设置为460800}}
}//DMA传输完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{uint8_t i;if(hadc->Instance == ADC1){for(i = 50; i < 100; i++)//处理另一半数据{printf("$%.5f;\n", 3.3 * ADC_RX_BUF[i] / 4095);}}
}
/* USER CODE END 1 */
……
//File main.c
……
uint16_t ADC_RX_BUF[100];int main(void)
{//启动ADC,使能DMA,数据长度为100HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_RX_BUF, 100);while(1){}
}
……

实验结果如下

STM32之ADC的学习心得(std+HAL)相关推荐

  1. STM32学习心得二十六:DAC数模转换实验

    记录一下,方便以后翻阅~ 主要内容: 1) DAC数模转换原理: 2) 寄存器和库函数介绍: 3) 相关实验代码解读. 实验功能:系统启动后,按WK_UP键,输出电压加200点,对应电压值200*3. ...

  2. STM32学习心得:SPI-Flash-W25Q16DV

    前言 目前正在学习STM32单片机的基础知识,通过库函数实现想要的一些功能.这篇文章主要介绍的是片外的Flash的操作,Flash的型号是W25Q16DV(芯片介绍在后面),通信的方式是SPI通信. ...

  3. STM32学习心得二十四:内部温度传感器原理及实验

    记录一下,方便以后翻阅~ 主要内容: 1) STM32内部温度传感器概述: 2) 相关实验代码解读. 实验功能:系统启动后,实时将内部温度传感器的值传至串口助手上. 官方资料:<STM32中文参 ...

  4. 10天学会STM32的学习心得总结

    01  前言  有读者问,如何系统地入门学习stm32呢? 假如你会使用8051 , 会写C语言,那么STM32本身并不需要刻意的学习. 我们要考虑的是, 我可以快速用STM32实现什么? 为什么使用 ...

  5. STM32学习心得十九:电容触摸按键实验及相关代码解读

    记录一下,方便以后翻阅~ 主要内容 1) 电容触摸按键原理: 2)部分实验代码解读. 实验内容 手触摸按键后,LED1灯翻转. 硬件原理图 上图,TPAD与STM_ADC用跳线帽相连,即TPAD与PA ...

  6. STM32学习心得二十一:实时时钟RTC和备份寄存器BKP特征、原理及相关实验代码解读

    记录一下,方便以后翻阅~ 主要内容 1) RTC特征与原理: 2) BKP备份寄存器特征与原理: 3) RTC常用寄存器+库函数介绍: 4) 相关实验代码解读. 实验内容: 因为没有买LCD屏,所以计 ...

  7. STM32学习心得十八:通用定时器基本原理及相关实验代码解读

    记录一下,方便以后翻阅~ 主要内容: 1) 三种定时器分类及区别: 2) 通用定时器特点: 3) 通用定时器工作过程: 4) 实验一:定时器中断实验补充知识及部代码解读: 6) 实验二:定时器PWM输 ...

  8. STM32学习心得十七:窗口看门狗(WWDG)实验及旧知识点复习

    记录一下,方便以后翻阅~ 主要内容: 1) 窗口看门狗概述: 2) 常用寄存器和库函数配置: 3) 窗口看门狗实验. 窗口看门狗实验内容: 为了对之前的知识进行总结复习,本人在教学案例的基础上又&qu ...

  9. 使用HAL库开发STM32:ADC基础使用

    文章目录 目的 基础说明 基础使用 配置选项说明 轮询 单通道 单次 轮询 单通道 连续 轮询 多通道 扫描 中断 单通道 中断 多通道 扫描 DMA 单通道 单轮 DMA 单通道 连续 DMA 多通 ...

最新文章

  1. linux 为开发板添加板级文件config.h
  2. 利用koa实现mongodb数据库的增删改查
  3. Visual Studio 2017 15.5.0 正式发布 正式版下载
  4. [C++][线程安全]单例模式下双检查锁和线程
  5. 嵌入式Linux系统之I.MX6触摸屏驱动程序TSC2007.C的分析、移植与校准
  6. react api_如何在WordPress REST API之上构建React应用
  7. 2020.xilinx开发环境
  8. Linux查询命令man手册各章节解释
  9. f2fs解析(四)f2fs的extent特性
  10. 中国1km分辨率的DEM数据以及合并后的中国行政区划数据
  11. WireGuard简单配置
  12. Leetcode 120. Triangle 三角形问题(动态规划经典) 解题报告
  13. cad2017单段线_cad2017新功能介绍
  14. C++常用的音频工具库
  15. 【译】将字符转换为双精度浮点型
  16. 确定电气间隙和爬电距离
  17. Android设备获取mp3中的专辑封面信息
  18. Java 生成 OFD 文档
  19. vscode报错之 对修饰器的实验支持功能在将来的版本中可能更改。在 “tsconfig“ 或 “jsconfig“ 中设置 “experimentalDecorators“ 选项以删除此警告。
  20. 微信公众号config:invalid signature签名失效

热门文章

  1. 第100章 SQL函数 NULLIF
  2. 判断这一天是这一年的第几天? 的三种方法! Java
  3. 阿里云——OSS的创建和使用
  4. 如何玩转网络攻防?看这文就够了!
  5. chmod命令用法(linux中chmod命令用法)
  6. Quartus Prime 与 Modelsim 调试 及do文件使用
  7. 062_《Delphi7完美经典》
  8. jpg格式图片压缩怎么弄?怎么把jpg图片压缩小?
  9. 对网易云音乐参数(params,encSecKey)的分析
  10. Linux调试排错命令