ADC
计算机的世界是0和1的。单片机可以通过读取0和1来确定按键状态,也可以输出0和1来控制LED。即使是看起来不太0和1的PWM,好像可以输出0到5V之间的电压一样,达到0和1之间的效果,但本质上还是高低电平。
但是,世界上终究还是有0和1无法表示的。如果引脚上被施加0到5V之间的电压,寄存器PINx无法告诉我们具体情况,只能指示这个电压是1.5V以下还是3V以上(参考数据手册“Electrical characteristics”)。这种可以连续变化的信号称为模拟信号,与离散的、只能取0或1(0或5V)的数字信号对立。
这并不代表数字世界无法处理模拟信号,相反,一种相当常用的处理模拟信号的方法,就是把模拟信号转换成数字信号,用处理器来运算,然后再转换成模拟信号。这个过程中涉及到模拟-数字转换和数字-模拟转换,分别需要ADC和DAC来实现。大多数单片机,作为现实世界中的工具,需要接触模拟信号,尤其是模拟信号的输入,会集成ADC。
ADC的一个参数是分辨率,指它的位数,反映了可以产生的不同输出的数量(8位ADC可以产生0~255的值)与量化最小物理量(通常是电压)的能力(比如当参考电压为2.56V时,理想情况下,8位ADC可以分辨两个相差0.01V的电压的不同)。AVR单片机带有的ADC是10位的。
另一个参数是转换速率,每秒进行A/D转换的次数。AVR单片机的ADC为了达到10位分辨率的精度,最大转换速率为15kSPS(千次采样每秒)。如果可以接受较低的精度,也可以以200kSPS采样,获得8位数据。
分辨率与精度是不同的概念。在这篇入门级教程中,我们只需要知道,A/D转换是会有误差的(数据手册23.7.4一节介绍了可能的误差来源)。即使是相同的电压,两次测量的结果也可能是不同的。
要进行A/D转换,需要提供参考电压和待测电压,转换的结果为待测电压参考电压×2分辨率。寄存器ADMUX中的ADLAR位控制转换结果的对齐方式。当右对齐时,公式中分辨率取10,转换结果在16位寄存器ADC中(实际上是两个8位寄存器ADCH与ADCL,但程序可以直接使用ADC,编译器会处理好一些注意事项);当左对齐时,分辨率取8,转换结果在ADCH中。可以直接把ADC当做16位寄存器,编译器会处理好一些注意事项。
ADC有4种参考电压可供选择,分别是AREF、AVCC(5V)、1.1V和2.56V,由REFS1:0选择。8个单端端口(开发板上引出了4个,端口0到3),以及一些差分端口(1x、10x、200x增益)和两个参考电压,共32个通道,可以通过多路复用器连接到ADC上进行转换,由MUX4:0选择。注意,ADC只有一个,在同一时刻只能转换一个通道的电压。
ADCSRA和ADCSRB用于控制A/D转换。ADCSRA中ADEN启用ADC组件,ADSC位启动一次转换,到ADIF位为1时转换结束,需要写1才能清零。ADPS2:0选择ADC时钟分频系数,这关系到转换速率:首次采样(启用ADC后第一次或同时)需要25个ADC时钟周期,随后每次采样需要13个。ADCSRB可以选择A/D转换触发源。
开发板提供了3.3V电源,可用于给只支持3.3V的设备供电。我们用ADC来测量这个电压,然后在串口上输出。
#include #include int main() { uart_init(UART_TX); ADMUX = 0b01 << REFS0 // AVCC as reference | 0b0 << ADLAR // right adjust | 0b00000 << MUX0; // ADC0 single ended ADCSRA = 1 << ADEN // enable ADC | 1 << ADSC // start conversion | 1 << ADIF // clear flag | 0b111 << ADPS0; // divide by 128 while (!(ADCSRA & 1 << ADIF)) // wait until flag is set ; uint16_t voltage = (uint32_t)ADC * 500 >> 10; // ADC / 1024 * 500 (* 10mV) uint8_t integer = 0; // integer part of voltage while (integer * 100 <= voltage) // calculate integer part ++integer; --integer; uint8_t decimal = voltage - integer * 100; // calculate decimal part uart_print_int(integer); // print the voltage uart_print_char('.'); uart_set_align(UART_ALIGN_RIGHT, 2, '0'); uart_print_int(decimal); uart_print_string("Vn"); while (1) ; } 数据手册28.8节指明,当ADC时钟为200kHz时,ADC绝对精度可以达到1.9LSB(1LSB就是1024中的1)。经计算得,为了使ADC时钟不超过这个速率,分频系数应该取128。 所测电压为voltage=ADC1024×5V,但直接这样计算会涉及到浮点运算,而AVR硬件不支持浮点,所有浮点运算都是软件实现的,速度相当慢,两个float相乘需要1000多个指令周期,除法需要更多,都是应该竭力避免的。尽管最后的电压是一个小数,但可以通过移动小数点把它变成整数。5V参考电压下,精度1.9LSB约为9.28mV,因此右移两位,以10mV为单位计算。先算乘法以避免浮点除法,算式变为voltage=ADC×5001024。 ADC的值直接与500相乘会溢出,因此需要先提升为uint32_t。当然,你可以把算式约分一下,但不改变会溢出的事实。尽管32位整数不太好处理,但相比浮点数还是容易得多。然后是一个除法。16位整数除法需要173个CPU指令周期(参考:Multiply and Divide Routines),是比较耗时的。尽管这个程序中只计算一次,但还是应该尽量想办法避免耗时的操作。注意到除数1024是一个特殊的数,是2的10次方,可以通过移位运算来做除法,而移位运算相比除法快得多(也许编译器会把/ 1024优化成>> 10)。 然后我们需要把这个数的百位部分拿出来作电压的整数部分,十位和个位作小数部分,可以通过除以100和模100来实现。由于这里的100是一个编译期常数,编译期很可能把这个除法和取模优化掉,不调用100多周期的过程。这里我们感受一下手动优化。由于变量voltage一定小于500,可以用乘法和比较的循环来试出这个商,其中乘法的执行次数不超过6次——AVR单片机有双周期乘法指令。然后,用乘法与减法求出余数。 ADC是单片机编程中相对容易用到浮点与乘除法的场合,设计算法时应尽量注意避免耗时的运算,或手动编写优化的算法来代替。 电位器 电位器,开发板右侧两个旋钮中左边一个,可以连续转动300°。电气属性相当于物理实验中的滑动变阻器,如果把两个定片接在VCC和GND上,动片电压就可以指示旋钮旋转的角度,并且通常与角度是成正比的。 之前提到过,A/D转换是有误差的,即使输入电压保持不变,转换结果也可能上下浮动。如果再加上一些电磁干扰,比如附近有电机,这种噪音会更加明显。如果一个程序需要检测电位器旋转的位置在中点的哪边,并仅仅是简单地比较转换结果与128的大小关系,这种噪声会导致严重后果,如红色波形所示: 在阈值128附近,噪声使转换结果上下浮动,导致判断出的状态迅速跳变。用户只是慢慢地把旋钮转过中间的位置,这显然不是我们想要的结果。 这时候就需要滞回比较器出场了。滞回比较器的核心特性是,使输出在0和1之间改变的输入阈值在两个方向上是不同的:当信号从低到高越过高阈值时,输出变为1;当信号从高到低越过低阈值时,输出变为0;如绿色波形所示(图中是反相的)。于是,当输入达到高阈值时,输出变为1,此时只要噪音没有大到使输入回到低阈值,输出将一直保持为1,滤除了噪声。 我们写一个程序,用LED来指示电位器旋钮位置在中点的哪一侧,并在串口上输出每一次状态改变,方便我们观察。 #include #include #include #include void init(); void normal(); void hysteresis(); int main() { init(); while (1) { normal(); // hysteresis(); delay(1); } } static bool status; void change(bool _value) { status = _value; uart_print_string(_value ? "onn" : "offn"); led_set(LED_BLUE, _value); } void init() { pot_init(ADC_0); led_init(); uart_init(UART_TX); status = pot_read() >= 128; } void normal() { bool now = pot_read() >= 128; if (status != now) change(now); } void hysteresis() { uint8_t pot = pot_read(); if (status && pot < 124) change(0); else if (!status && pot >= 132) change(1); } normal和hysteresis函数二选一,其中后者使用了滞回比较的算法。 在normal模式下,把电位器调整到中点附近的一个位置,你会发现黄色的TX指示灯发了疯一样地闪,串口软件显示一长串的“on”和“off”(仔细调,一定会有)——你根本不需要制造任何干扰,仅凭ADC的误差就可以让程序运行地非常糟糕。如果用满10位的分辨率,这样的现象会更加明显。 而在hysteresis模式下,这样的状况不会出现。 光敏电阻 光敏电阻是一种特殊的电阻器,在光强的时候电阻小,在光弱的时候电阻大。将一个光敏电阻与一个普通电阻串联,接在VCC和GND之间,测量中间点的电压,就能知道光的强弱。 当然,已知开发板上与光敏电阻串联的电阻是10kΩ,根据某一时刻的ADC转换结果,也可以计算出此时光敏电阻的阻值。不过不要误会,是通过电压而不是阻值来获得光强。 与电位器一样,如果要检测光的强与弱两种状态,也要用到滞回比较。取两个阈值为100和150,两者相差较大,这是因为我们要在光较弱时开灯,这又会增强亮度(有点负反馈的意味),如果相差不够大,就会陷入循环当中。 这两个阈值是随便取的,实际应用应根据具体环境取值。于是容易想到要把这个功能从应用程序中抽离出来成为一个库。但是,不同于之前常用的、返回外设状态让客户来决定操作的函数(尽管还是可以这么写),这个库是事件驱动的:客户注册事件发生时要执行的动作,把程序流程交给框架来控制。 程序分为三个文件:event.h、event.c和main.c,前两个可以独立成库,供以后使用,为了方便,和可执行程序放在一起了。 event.h: #ifndef EVENT_H #define EVENT_H #include #include void ldr_event_init(uint8_t _thl, uint8_t _thh, void (*_func)(bool)); void ldr_event_cycle(); #endif event.c: #include "event.h" #include static void (*handler)(bool); static uint8_t low, high; static bool status; void ldr_event_init(uint8_t _thl, uint8_t _thh, void (*_func)(bool)) { ldr_init(ADC_1); low = _thl; high = _thh; handler = _func; uint8_t ldr = ldr_read(); if (ldr <= low) handler(status = 0); else handler(status = 1); } void ldr_event_cycle() { uint8_t ldr = ldr_read(); if (status && ldr <= low) handler(status = 0); else if (!status && ldr >= high) handler(status = 1); } main.c: #include #include #include "event.h" void handler(bool e) { if (e) led_off(); else led_on(); } int main() { led_init(); ldr_event_init(100, 150, handler); while (1) { ldr_event_cycle(); delay(1000); } } 客户先编写事件处理函数handler,参数为一个bool,返回void,这是ldr_event_init所规定的。handler根据参数执行相应动作:当e为true时,光由弱变强,关灯;反之开灯。在调用ldr_event_init时,把这个函数的指针作为参数传入。随后,每隔1秒调用一次ldr_event_cycle。 请先花一点时间,把库的每一行理解清楚。然后,我们站在客户的角度来看,使用这个库是相对方便的——只需考虑事件,即光的变化,而无需考虑过程,即如何检测这一变化——事实上客户根本没有去检测,更别说如何了。不过,main函数必须每隔一段时间调用一次ldr_event_cycle。在学了定时器中断以后,main函数就可以完全还给客户了。
上一篇:BMP180测量海拔高度传感器单片机程序
下一篇:ATmega328P定时器详解
推荐阅读
史海拾趣
为了进一步提升公司的规模和实力,EMS GmbH公司积极寻求并购和资源整合的机会。通过并购其他具有技术优势和市场资源的公司,EMS GmbH公司能够快速扩大生产规模、提升技术水平和市场份额。同时,公司还注重与被并购公司的资源整合和协同发展,以实现双方的优势互补和互利共赢。这些并购和资源整合的举措使EMS GmbH公司在电子行业中的竞争地位得到了进一步巩固和提升。
请注意,以上故事仅为框架性描述,并未包含具体的细节和数据。在实际撰写时,您可以根据具体情况进行补充和完善。
Exar公司一直注重全球布局和客户支持。公司在全球设立了多个研发基地和客户服务中心,以确保能够为客户提供及时、高效的技术支持和服务。此外,Exar还积极与全球各地的合作伙伴建立合作关系,共同推动电子行业的发展和进步。这种全球化的战略布局不仅为Exar赢得了更多的市场份额和客户信任,也为其未来的发展奠定了坚实的基础。
以上五个故事简要概述了Exar在电子行业中的重要发展历程。通过这些故事,我们可以看到Exar如何通过不断的技术创新、市场拓展和全球化战略来保持其在行业中的领先地位。
Exar公司自1971年在加利福尼亚州成立以来,最初以生产高性能、高可靠性的线性集成电路而著名。然而,随着技术的进步和市场的变化,Exar逐渐意识到通信领域的巨大潜力。因此,在1980年代初,公司开始专注于串行通信和数据存储解决方案,包括UARTs(通用异步收发器)和存储器ICs。这一转变不仅扩大了Exar的市场覆盖范围,也为其后续的发展奠定了坚实的基础。
面对日益激烈的市场竞争,First Silicon公司深知供应链管理的重要性。公司投入大量资源,对供应链进行了全面整合和优化。通过建立长期稳定的供应商关系、引入先进的供应链管理软件和提高库存周转率等措施,First Silicon有效降低了原材料成本和库存风险。同时,公司还加强了与下游客户的沟通和合作,确保产品能够及时、准确地送达客户手中。这一系列举措不仅提升了公司的运营效率,还增强了其市场竞争力。
Eurofarad公司深知品质是企业生存和发展的根本。因此,公司一直注重品质管理,建立了严格的质量控制体系。同时,公司还不断提升服务水平,为客户提供全方位的售前咨询和售后服务。这些措施不仅提高了客户的满意度和忠诚度,还为公司的口碑和品牌形象增添了光彩。
港源公司深知品质是企业生存之本。公司先后通过了ISO9001质量管理体系认证、ISO14001环境体系认证、ISO45001职业健康安全管理体系认证以及IATF16949汽车质量体系认证等国际权威认证。这些认证不仅证明了港源公司在品质管理方面的卓越能力,还为公司赢得了国内外客户的广泛信赖和好评。港源公司坚持“诚信、创新、和谐、共赢”的企业核心价值观,致力于为客户提供高品质、高可靠性的产品和服务。
我有两个.msi安装包,A.msi和B.msi。 在windows xp下都能正确安装。 但在XPE中一个能安装,一个就失败,报错“This installation package could not be opened. Contact the application vendor to verify that this is a valid Windows Installe ...… 查看全部问答∨ |
|
这个问题比较可笑吧。 可是我就是搞不定, 现在网上down下了一个例子程序,是读出SD卡的一个test.txt文件。 BOOL CSDMMCDlg::OnInitDialog() { CDialog::OnInitDialog(); // Set the i ...… 查看全部问答∨ |
Clock jitter analyzed in the time domain Part 1 本帖最后由 dontium 于 2015-1-23 13:39 编辑 IntroductionNewer high-speed ADCs e outfittedwith a large analog-input bandwidth (aboutthree to six times the maximum samplingfrequency) so they can be used in undersamplingapplications. ...… 查看全部问答∨ |
请教,在SPI的从模式下,怎么发送一个字节的数据给主机,看STM32的手册上好象,MOSI和MISO是在同一个时钟边沿被被 ...… 查看全部问答∨ |
int main(void){ // disable JTAG port DDPCONbits.JTAGEN = 0; //T2 interrupt initialization SEI();//打开总中断 IFS0bits.T2IF=0;//清零中断标志位& ...… 查看全部问答∨ |
本帖最后由 paulhyde 于 2014-9-15 03:27 编辑 请问各位有没有做过在高频100M的有关MV级小信号放大及大信号衰减为mv级的小信号的模块?主要用过的芯片有哪些推荐啊?谢谢了!!! … 查看全部问答∨ |
|
mSATA (mini-SATA)是迷你版本SATA接口,外型和电子介面与mini PCI-E完全相同,但电子信号不同,两者互不兼容。 中文名:迷你版本SATA接口 外文名:mSATA 别称:mini-SATA 公司:SATA协会 分享 概念 mSATA图片 图册 5张 msata是SATA协会 ...… 查看全部问答∨ |