• stdarg的用法(可变参数的用法)

     stdarg宏: 可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,它是标准库的一部分。 这个头文件声明的一个va_list的类型,和三个宏va_start,va_arg,va_end。我们可以生明一个va_list类型的变量,配合三个宏使用。 va_start(arg, last have name arg); 初始化之后,arg将指向第一个无名参数。 va_arg(arg, next arg type); va_arg将会返回当前的arg的va_list变量所指向的无名变量。并使它指向下一个无名变量。 注意,当访问所有变量之后记得调用va_end(arg); 来释放这个va_list类型的变量。 #include int nsum(int n,...) { va_list num; // va_list 是一个宏定义类型 int sum=0; va_start(num,n); //开始取参,是num指向第一个参数 for(;n>1;n--) { sum += va_arg(num,int); // 这个函数返回当前指向的参数,并指向下一个参数 } va_end(num); //用完释放 return sum; }

    时间:2018-05-14 关键词: C语言 stdarg

  • 汇编C语言混合编程经验总结

     ARM汇编语言和C语言混合编程 ATPCS规则体现了一种模块化设计的思想,其基本内容是C模块(函数)和汇编模块(函数)相互调用的一套规则(C51中也有类似的一套规则)。我感觉比在线汇编功能强大(不用有很多忌讳),条理更清楚(很简单的几条规则)。 ATPCS规则内容: 1)寄存器的使用规则 1、子程序之间通过寄存器r0~r3来传递参数,当参数个数多于4个时,使用堆栈来传递参数。此时r0~r3可记作A1~A4。 2、在子程序中,使用寄存器r4~r11保存局部变量。因此当进行子程序调用时要注意对这些寄存器的保存和恢复。此时r4~r11可记作V1~V8。 3、寄存器r12用于保存堆栈指针SP,当子程序返回时使用该寄存器出栈,记作IP。 4、寄存器r13用作堆栈指针,记作SP。寄存器r14称为链接寄存器,记作LR。该寄存器用于保存子程序的返回地址。 5、寄存器r15称为程序计数器,记作PC。 2)堆栈的使用规则 ATPCS规定堆栈采用满递减类型(FD,Full Descending),即堆栈通过减小存储器地址而向下增长,堆栈指针指向内含有效数据项的最低地址。 3)参数的传递规则 1、整数参数的前4个使用r0~r3传递,其他参数使用堆栈传递;浮点参数使用编号最小且能够满足需要的一组连续的寄存器传递参数。 2、子程序的返回结果为一个32位整数时,通过r0返回;返回结果为一个64位整数时,通过r0和r1返回;依此类推。结果为浮点数时,通过浮点运算部件的寄存器F0、D0或者S0返回。 比较有条理,很清楚,我举两个例子: 1.汇编主程序调用C子程序 汇编程序的书写要遵循ATPCS规则,以保证程序调用时参数正确传递。在汇编程序中调用C程序的方法为: 1)在汇编程序中使用IMPORT伪指令或者extern事先声明将要调用的C语言函数; 2)通过BL指令来调用C函数。 例如在一个C源文件中定义了如下求和函数: int add(int x,int y) { return(x+y); } 调用add()函数的汇编程序结构如下: area main,code,readonly ;代码段 entry ;声明程序入口 code32 ;32位ARM指令 IMPORT add 或者extern add;声明要调用的C函数 start …… MOV r0,1 MOV r1,2 BL add ;调用C函数add …… end 当进行函数调用时,使用r0和r1实现参数传递,返回结果由r0带回。函数调用结束后,r0的值变成3。 2.C主程序调用汇编子程序 C程序调用汇编程序时,汇编程序的书写也要遵循ATPCS规则,以保证程序调用时参数正确传递。在C程序中调用汇编子程序的方法为: 1)在汇编程序中使用EXPORT伪指令声明被调用的子程序,表示该子程序将在其他文件中被调用; 2)在C程序中使用extern关键字声明要调用的汇编子程序为外部函数。 例如在一个汇编源文件中定义了如下求和函数: EXPORT add ;声明add子程序将被外部函数调用 …… add ;求和子程序add .global add ;声明 ADD r0,r0,r1 MOV pc,lr …… 在一个C程序的main()函数中对add汇编子程序进行了调用: extern int add (int x,int y); //声明add为外部函数 void main(){ int a=1,b=2,c; c=add(a,b); //调用add子程序,并且隐式地对r0和r1赋值 …… } 当main()函数调用add汇编子程序时,变量a、b的值会给了r0和r1,返回结果由r0带回,并赋值给变量c。函数调用结束后,变量c的值变成3。 3、C程序中内嵌汇编语句 在C语言中内嵌汇编语句可以实现一些高级语言不能实现或者不容易实现的功能。对于时间紧迫的功能也可以通过在C语言中内嵌汇编语句来实现。内嵌的汇编器支持大部分ARM指令和Thumb指令,但是不支持诸如直接修改PC实现跳转的底层功能,也不能直接引用C语言中的变量。 内嵌汇编:在C和C++语言中嵌入汇编语言可以实现一些高级语言中没有的功能。 语法 __asm__( ;注意:前面是两个“_” “instruction ... instruction” );//Linux gcc中支持 __asm{ instruction ... instruction }; //ADS中支持 asm(“instruction[; instruction]”); //ARM C++中使用 ARM内嵌汇编语法 asm( 汇编语句模板: 输出部分: 输入部分: 修改部分 ); 比如: asm("mov %0, %1, ror #1" :"=r" (result) : "r" (value)); 共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。例如: __asm__ __volatile__( "CLI": :"memory" ); 示例:/* main.c */ void __main(void) { int var=0xAA; __asm //内嵌汇编标识 { MOV R1,var CMP R1,#0xAA } while(1); }

    时间:2018-05-14 关键词: 汇编语言 C语言

  • 51单片机实现scanf和printf函数

     最开始学习C语言时,使用printf和scanf进行格式化输入输出十分方便。 学习单片机有很长时间了,之前要再屏幕上显示一个变量或者通过串口传出一些变量值观测的话,需要进行一系列的取余取整运算,很是麻烦。 最近又研究了一下keil中针对printf和scanf的实现机理,做了一些改动,实现了标准格式化输入输出,共大家参考。 1.printf函数在格式化输出时,向下调用了char putchar(char c);这个函数,在“stdio.h”里可以发现有这个函数,所以我们需要自己构造一个这样的函数,即通过串口putchar(),代码如下: [cpp] view plain copychar putchar(char c) { hal_uart_putchar(c); return c; } 其中hal_uart_putchar(c);函数是我们比较熟悉的了,是51单片机通过串口发送一个字节的函数,具体代码如下: [cpp] view plain copyvoid hal_uart_putchar(char i) { ES = 0; TI = 0; //清空发送完中断请求标志位 SBUF = i; //将数据放入寄存器发送 while(TI == 0);//等待发送完毕,发送完毕 TI == 1 TI = 0; //清空发送完中断请求标志位 ES = 1; } 有了这两个函数,在单片机启动后,首先进行串口初始化,接着就可以使用printf了……是不是很简单…… ------------------------------------------------------------------------------------------------------------------------------------- 2.下面再看scanf的具体实现方法: scanf函数在格式化输入时,向下掉用了char getkey(void);这个函数,在“stdio.h”里可以发现有这个函数,所以我们需要自己构造一个这样的函数,即通过串口getkey(),代码如下: [cpp] view plain copychar _getkey (void) { return hal_uart_getchar(); } 其中hal_uart_getchar(); 稍稍复杂,但也很好理解,代码如下: [cpp] view plain copychar hal_uart_getchar(void) { uchar ch; //Wait until a character is available: while(uart_rx_cnt == 0); ES = 0; ch = uart_rx[uart_rx_rp]; uart_rx_rp = (uart_rx_rp + 1) % UART_BUF_SIZE; uart_rx_cnt--; ES = 1; return ch; } 这个函数是从串口接收队列中取出队尾的一个字节。uart_rx_cnt 表示现在串口队列中的已有字节数,uart_rx_rp 指向队尾。 最后要介绍的一个函数是串口接收中断函数,代码如下: [cpp] view plain copyvoid UART1InterruptReceive(void) interrupt 4 { ES=0;//关串行口中断 if(RI) { RI=0;//接收中断信号清零,表示将继续接收 if(uart_rx_cnt < UART_BUF_SIZE) { uart_rx[uart_rx_wp] = SBUF; uart_rx_wp = (uart_rx_wp + 1) % UART_BUF_SIZE; uart_rx_cnt++; } } ES=1;//开串行口中断 } 该函数实现了串口的中断接收,收到的新的字节存放在队首,即uart_rx_wp指向队列的首地址,每次收到一个新的字节,uart_rx_cnt增1。 至此,scanf函数也可以实现了。 测试截图: 注:串口接收的队列没有溢出检测…… 这篇文章里实现的是对于串口的格式化输入输出,实际上,我们同样可以对hal_uart_getchar();和hal_uart_putchar(c);函数进行更改,实现在屏幕上的格式化输出等,思路都是一样的…… 有不合理的地方,请大家批评指正。

    时间:2018-05-14 关键词: 51单片机 C语言

  • 51系列单片机的系统时钟如何产生?

     单片机的控制器的定时功能是由时钟和定时电路完成的,它是产生CPU的操作时序。 XTAL1是芯片内部振荡电路输入端,XTAL2为芯片内部振荡电路输出端 具体的产生有以下两种方式: 一:内部方式 则是利用芯片内反相器和电阻组成的振荡电路,,在XTAL1和XTAL2引脚上接定时元件,如压电晶体和电容组成的并联谐振电路,则内部可产生与外加晶体同频率的振荡时钟。一般晶体可以在1.2MHZ到12MHZ之间任意选择,电容一般选择在5pf到30pf,对时钟频率有微调作用。 二:外部时钟方式 如果采用外部时钟方式,此时要把XTAL1接到外部始终提供电路,XTAL2接地。这种情况一般是当整个单片机系统已经有时钟源或则在多机系统中取得时钟上的同步。 注: 1:系统时钟是CPU时钟,RCT(Real—Time—Clock)是实时时钟。 2:系统时钟的目的是高速稳定,而实时时钟目的是低功耗精确。

    时间:2018-05-14 关键词: 时钟 单片机

  • STM32单片机和51单片机有何区别?

     单片机简介 单片微型计算机简称单片机,简单来说就是集CPU(运算、控制)、RAM(数据存储-内存)、ROM(程序存储)、输入输出设备(串口、并口等)和中断系统处于同一芯片的器件,在我们自己的个人电脑中,CPU、RAM、ROM、I/O这些都是单独的芯片,然后这些芯片被安装在一个主板上,这样就构成了我们的PC主板,进而组装成电脑,而单片机只是将这所有的集中在了一个芯片上而已。 51单片机和STM32单片机 51单片机是对所有兼容Intel8031指令系统的单片机的统称,这一系列的单片机的始祖是Intel的8031单片机,后来随着flash ROM技术的发展,8031单片机取得了长足的进展成为了应用最广泛的8bit单片机之一,他的代表型号就是ATMEL公司的AT89系列。 STM32单片机则是ST(意法半导体)公司使用arm公司的cortex-M为核心生产的32bit系列的单片机,他的内部资源(寄存器和外设功能)较8051、AVR和PIC都要多的多,基本上接近于计算机的CPU了,适用于手机、路由器等等。 DSP、AVR和PIC单片机、8051单片机之间区别 AVR和PIC都是跟8051单片机的机构不同的8位单片机,因为结构不同,所以他的汇编指令也不同,并且他们都是使用的RISC指令集,只有几十条指令,大部分的还都是单周期的指令,所以在相同的晶振频率下,比8051速度要快。 DSP其实也是一种特殊的单片机,他从8bit到32bit的都有,他专门是用来计算数字信号的,在某些计算公式上,他甚至比现在的家用计算机的最快CPU还要快,比如说一个32bit的DSP能在一个指令周期内完成一个32bit数乘以32bit数再加上一个32bit数的计算。 8051、8031、89C51和89S51关系 我们平常老是讲8051,又有什么8031,现在又有89C51,89s51它们之间究竟是什么关系? MCS51是指由美国INTEL公司生产的一系列单片机的总称,这一系列单片机包括了好些品种,如8031,8051,8751,8032,8052,8752等,其中8051是最早最典型的产品,该系列其它单片机都是在8051的基础上进行功能的增、减、改变而来的,所以人们习惯于用8051来称呼MCS51系列单片机,而8031是前些年在我国最流行的单片机,所以很多场合会看到8031的名称。INTEL公司将MCS51的核心技术授权(卖)给了很多其它公司,所以有很多公司在做以8051为核心的单片机,当然,功能或多或少有些改变,以满足不同的需求,其中89C51就是这几年在我国非常流行的单片机。至于国内用到的很多的AT系列的单片机其实就是ATMEL公司在8031内核之外添加其他功能生产了系列的单片机。 这里要补充说明下,最先出现先的单片机其实是Intel公司的8031单片机,他是单片机的鼻祖,但是它本身是没有内部程序存储器的,之后随着flash ROM技术的发展,出现了能够存储程序的8051系列单片机

    时间:2018-05-14 关键词: STM32 51单片机

  • 单片机系统时钟与实时时钟有什么区别?

     1、大多数单片机都只有系统时钟一个。就是CPU的各节拍工作时序的驱动源了。这个频率一般为几MHz。速度比较快,其目的无非是让单片机快点干活。那为啥不是GHz数量呢,这个是集成电路工艺决定的。根据工艺反推出某款单片机的理想工作频率,往往也是最佳工作频率了。系统在这个频率下工作又快(已达最佳极限)又稳定。最好地体现了计算机的高速运算能力。 2、实时时钟,是单片机计时的时钟或独立的可被单片机访问的时钟。它可以外部扩展芯片得到,如1302,1307,12887,3130,12020,m41t81,6902,8025。有并口有串口,有带电池自己玩,有外部供电,看实际需要设计。这些时钟无一例外地用到了32768Hz。这是因为它们用了同一个计时IC核、低频功耗更低、更容易校表和1Hz计时精密实现。大伙在该基础上做了不同的文章,有的搞点稳定晶振放里面,有的搞点备电方案,有的接口不同,有的搞点万年历,有的搞点报警,有的…… 3、还有可能你提到的(可能就是430系列单片机),内部集成了RTC这个模块,要求外面接32768Hz。这样就可以独立地计时,单片机睡觉了也和它的时间管理无关,低成本实时方案,又省了好几毛。综上: 【1】系统时钟就是CPU时钟,RTC时钟就是计时时钟。 【2】系统时钟的目的是高速稳定,而实时时钟目的是低功耗精确。

    时间:2018-05-14 关键词: 时钟 单片机

  • 模电学习八大概念,工程师必看

     在电子电路中,电源、放大、振荡和调制电路被称为模拟电子电路,因为它们加工和处理的是连续变化的模拟信号。 1. 反馈 反馈是指把输出的变化通过某种方式送到输入端,作为输入的一部分。如果送回部分和原来的输入部分是相减的,就是负反馈。 2. 耦合 一个放大器通常有好几级,级与级之间的联系就称为耦合。放大器的级间耦合方式有三种: ①RC耦合(见图a): 优点是简单、成本低。但性能不是最佳。 ② 变压器耦合(见图b):优点是阻抗匹配好、输出功率和效率高,但变压器制作比较麻烦。 ③ 直接耦合(见图c): 优点是频带宽,可作直流放大器使用,但前后级工作有牵制,稳定性差,设计制作较麻烦。 3. 功率放大器 能把输入信号放大并向负载提供足够大的功率的放大器叫功率放大器。例如收音机的末级放大器就是功率放大器。 3.1 甲类单管功率放大器 负载电阻是低阻抗的扬声器,用变压器可以起阻抗变换作用,使负载得到较大的功率。 这个电路不管有没有输入信号,晶体管始终处于导通状态,静态电流比较大,困此集电极损耗较大,效率不高,大约只有 35 %。这种工作状态被称为甲类工作状态。这种电路一般用在功率不太大的场合,它的输入方式可以是变压器耦合也可以是 RC 耦合。 3.2 乙类推挽功率放大器 下图是常用的乙类推挽功率放大电路。 它由两个特性相同的晶体管组成对称电路,在没有输入信号时,每个管子都处于截止状态,静态电流几乎是零,只有在有信号输入时管子才导通,这种状态称为乙类工作状态。当输入信号是正弦波时,正半周时 VT1 导通 VT2 截止,负半周时 VT2 导通 VT1 截止。两个管子交替出现的电流在输出变压器中合成,使负载上得到纯正的正弦波。这种两管交替工作的形式叫做推挽电路。 3.3 OTL 功率放大器 目前广泛应用的无变压器乙类推挽放大器,简称 OTL 电路,是一种性能很好的功率放大器。为了易于说明,先介绍一个有输入变压器没有输出变压器的 OTL 电路,如下图所示。 4. 直流放大器 能够放大直流信号或变化很缓慢的信号的电路称为直流放大电路或直流放大器。测量和控制方面常用到这种放大器。 4.1 双管直耦放大器 直流放大器不能用 RC 耦合或变压器耦合,只能用直接耦合方式。下图是一个两级直耦放大器。直耦方式会带来前后级工作点的相互牵制,电路中在 VT2 的发射极加电阻 R E 以提高后级发射极电位来解决前后级的牵制。 直流放大器的另一个更重要的问题是零点漂移。所谓零点漂移是指放大器在没有输入信号时,由于工作点不稳定引起静 态电位缓慢地变化,这种变化被逐级放大,使输出端产生虚假信号。放大器级数越多,零点漂移越严重。所以这种双管直耦放大器只能用于要求不高的场合。 4.2 差分放大器 解决零点漂移的办法是采用差分放大器,下图是应用较广的射极耦合差分放大器。它使用双电源,其中 VT1 和 VT2 的特性相同,两组电阻数值也相同, R E 有负反馈作用。实际上这是一个桥形电路,两个 R C 和两个管子是四个桥臂,输出电压 V 0 从电桥的对角线上取出。没有输入信号时,因为 RC1=RC2 和两管特性相同,所以电桥是平衡的,输出是零。由于是接成桥形,零点漂移也很小。差分放大器有良好的稳定性,因此得到广泛的应用。 5. 集成运算放大器 集成运算放大器是一种把多级直流放大器做在一个集成片上,只要在外部接少量元件就能完成各种功能的器件。因为它早期是用在模拟计算机中做加法器、乘法器用的,所以叫做运算放大器 6. 振荡器 不需要外加信号就能自动地把直流电能转换成具有一定振幅和一定频率的交流信号的电路就称为振荡电路或振荡器。这种现象也叫做自激振荡。或者说,能够产生交流信号的电路就叫做振荡电路。 一个振荡器必须包括三部分:放大器、正反馈电路和选频网络。放大器能对振荡器输入端所加的输入信号予以放大使输出信号保持恒定的数值。正反馈电路保证向振荡器输入端提供的反馈信号是相位相同的,只有这样才能使振荡维持下去。选频网络则只允许某个特定频率f0能通过,使振荡器产生单一频率的输出。 振荡器能不能振荡起来并维持稳定的输出是由以下两个条件决定的;一个是反馈电压Uf和输入电压 Ui要相等,这是振幅平衡条件。二是 Uf 和 Ui 必须相位相同,这是相位平衡条件,也就是说必须保证是正反馈。一般情况下,振幅平衡条件往往容易做到,所以在判断一个振荡电路能否振荡,主要是看它的相位平衡条件是否成立。 振荡器按振荡频率的高低可分成超低频( 20赫以下)、低频( 20赫~ 200千赫)、高频(200千赫~ 30兆赫)和超高频( 10兆赫~ 350兆赫)等几种。按振荡波形可分成正弦波振荡和非正弦波振荡两类。 正弦波振荡器按照选频网络所用的元件可以分成 LC 振荡器、 RC振荡器和石英晶体振荡器三种。石英晶体振荡器有很高的频率稳定度,只在要求很高的场合使用。在一般家用电器中,大量使用着各种 LC振荡器和 RC 振荡器。 6.1 LC振荡器 LC 振荡器的选频网络是LC 谐振电路。它们的振荡频率都比较高,常见电路有 3 种。 1) 变压器反馈 LC 振荡电路 图(a)是变压器反馈 LC 振荡电路。晶体管 VT 是共发射极放大器。变压器 T 的初级是起选频作用的 LC 谐振电路,变压器 T 的次级向放大器输入提供正反馈信号。接通电源时, LC 回路中出现微弱的瞬变电流,但是只有频率和回路谐振频率 f 0 相同的电流才能在回路两端产生较高的电压,这个电压通过变压器初次级 L1 、 L2 的耦合又送回到晶体管 V 的基极。从图(b)看到,只要接法没有错误,这个反馈信号电压是和输入信号电压相位相同的,也就是说,它是正反馈。因此电路的振荡迅速加强并最后稳定下来。 变压器反馈 LC 振荡电路的特点是:频率范围宽、容易起振,但频率稳定度不高。它的振荡频率是: f 0 =1/2π LC 。常用于产生几十千赫到几十兆赫的正弦波信号。 2) 电感三点式振荡电路 图(a)是另一种常用的电感三点式振荡电路。图中电感 L1 、 L2 和电容 C 组成起选频作用的谐振电路。从 L2 上取出反馈电压加到晶体管 VT 的基极。从图(b)看到,晶体管的输入电压和反馈电压是同相的,满足相位平衡条件的,因此电路能起振。由于晶体管的 3 个极是分别接在电感的 3 个点上的,因此被称为电感三点式振荡电路。 电感三点式振荡电路的特点是:频率范围宽、容易起振,但输出含有较多高次调波,波形较差。它的振荡频率是: f 0 =1/2π LC ,其中 L=L1 + L2 + 2M 。常用于产生几十兆赫以下的正弦波信号。 3) 电容三点式振荡电路 还有一种常用的振荡电路是电容三点式振荡电路,见图(a)。图中电感 L 和电容 C1 、 C2 组成起选频作用的谐振电路,从电容 C2 上取出反馈电压加到晶体管 VT 的基极。从图(b)看到,晶体管的输入电压和反馈电压同相,满足相位平衡条件,因此电路能起振。由于电路中晶体管的 3 个极分别接在电容 C1 、 C2 的 3 个点上,因此被称为电容三点式振荡电路。 电容三点式振荡电路的特点是:频率稳定度较高,输出波形好,频率可以高达 100 兆赫以上,但频率调节范围较小,因此适合于作固定频率的振荡器。它的振荡频率是: f 0 =1/2π LC ,其中 C= C 1 +C 2 。 上面 3 种振荡电路中的放大器都是用的共发射极电路。共发射极接法的振荡器增益较高,容易起振。也可以把振荡电路中的放大器接成共基极电路形式。共基极接法的振荡器振荡频率比较高,而且频率稳定性好。 6.2 RC 振荡器 RC 振荡器的选频网络是 RC 电路,它们的振荡频率比较低。常用的电路有两种。 1) RC 相移振荡电路 RC 相移振荡电路的特点是:电路简单、经济,但稳定性不高,而且调节不方便。一般都用作固定频率振荡器和要求不太高的场合。它的振荡频率是:当 3 节 RC 网络的参数相同时: f 0 = 1 2π 6RC 。频率一般为几十千赫。 2) RC 桥式振荡电路 RC 桥式振荡电路的性能比 RC 相移振荡电路好。它的稳定性高、非线性失真小,频率调节方便。它的振荡频率是:当 R1=R2=R 、 C1=C2=C 时 f 0 = 1 2πRC 。它的频率范围从 1 赫~ 1 兆赫。 7. 调幅和检波电路 广播和无线电通信是利用调制技术把低频声音信号加到高频信号上发射出去的。在接收机中还原的过程叫解调。其中低频信号叫做调制信号,高频信号则叫载波。常见的连续波调制方法有调幅和调频两种,对应的解调方法就叫检波和鉴频。 7.1 调幅电路 调幅是使载波信号的幅度随着调制信号的幅度变化,载波的频率和相位不变。能够完成调幅功能的电路就叫调幅电路或调幅器。 调幅是一个非线性频率变换过程,所以它的关键是必须使用二极管、三极管等非线性器件。根据调制过程在哪个回路里进行可以把三极管调幅电路分成集电极调幅、基极调幅和发射极调幅 3 种。下面举集电极调幅电路为例。 上图是集电极调幅电路,由高频载波振荡器产生的等幅载波经 T1 加到晶体管基极。低频调制信号则通过 T3 耦合到集电极中。 C1 、 C2 、 C3 是高频旁路电容, R1 、 R2 是偏置电阻。集电极的 LC 并联回路谐振在载波频率上。如果把三极管的静态工作点选在特性曲线的弯曲部分,三极管就是一个非线性器件。因为晶体管的集电极电流是随着调制电压变化的, 所以集电极中的 2 个信号就因非线性作用而实现了调幅。由于 LC 谐振回路是调谐在载波的基频上,因此在 T2 的次级就可得到调幅波输出。 7.2 检波电路 检波电路或检波器的作用是从调幅波中取出低频信号。它的工作过程正好和调幅相反。检波过程也是一个频率变换过程,也要使用非线性元器件。常用的有二极管和三极管。另外为了取出低频有用信号,还必须使用滤波器滤除高频分量,所以检波电路通常包含非线性元器件和滤波器两部分。下面举二极管检波器为例说明它的工作原理。 上图是一个二极管检波电路。 VD 是检波元件, C 和 R 是低通滤波器。当输入的已调波信号较大时,二极管 VD 是断续工作的。正半周时,二极管导通,对 C 充电;负半周和输入电压较小时,二极管截止, C 对 R 放电。在 R 两端得到的电压包含的频率成分很多,经过电容 C 滤除了高频部分,再经过隔直流电容 C0 的隔直流作用,在输出端就可得到还原的低频信号。 8. 调频和鉴频电路 调频是使载波频率随调制信号的幅度变化,而振幅则保持不变。鉴频则是从调频波中解调出原来的低频信号,它的过程和调频正好相反。 8.1 调频电路 能够完成调频功能的电路就叫调频器或调频电路。常用的调频方法是直接调频法,也就是用调制信号直接改变载波振荡器频率的方法。下图画出了它的大意,图中用一个可变电抗元件并联在谐振回路上。用低频调制信号控制可变电抗元件参数的变化,使载波振荡器的频率发生变化。 8.2 鉴频电路 能够完成鉴频功能的电路叫鉴频器或鉴频电路,有时也叫频率检波器。鉴频的方法通常分二步,第一步先将等幅的调频波变成幅度随频率变化的调频 — 调幅波,第二步再用一般的检波器检出幅度变化,还原成低频信号。常用的鉴频器有相位鉴频器、比例鉴频器等。

    时间:2018-05-09 关键词: 电子电路 模电

  • 模电学习的两个重点总结

     通观整本书,不外是,晶体管放大电路、场管放大电路、负反馈放大电路、集成运算放大器、波形及变换、功放电路、直流电源等。然而其中的重点,应该是场管和运放。 按理说,场管不是教材的重点,但目前实际中应用最广,远远超过双极型晶体管(BJT)。场效应管,包括最常见的MOSFET,在电源、照明、开关、充电等等领域随处可见。 运放在今天的应用,也是如火如荼。比较器、ADC、DAC、电源、仪表、等等离不开运放。 1、场效应管是只有一种载流子参与导电的半导体器件,是一种用输入电压控制输出电流的半导体器件。有 N 沟道和 P 沟道两种器件。有结型场管和绝缘栅型场管 IGFET 之分。IGFET 又称金属-氧化物-半导体管 MOSFET。MOS 场效应管有增强型 EMOS 和耗尽型 DMOS 两大类,每一类有 N 沟道和 P 沟道两种导电类型。 学习时,可将 MOSFET 和 BJT 比较,就很容易掌握,功率 MOSFET 是一种高输入阻抗、电压控制型器件,BJT 则是一种低阻抗、电流控制型器件。再比较二者的驱动电路,功率 MOSFET 的驱动电路相对简单。BJT 可能需要多达 20% 的额定集电极电流以保证饱和度,而 MOSFET 需要的驱动电流则小得多,而且通常可以直接由 CMOS 或者集电极开路 TTL 驱动电路驱动。其次,MOSFET 的开关速度比较迅速,MOSFET 是一种多数载流子器件,能够以较高的速度工作,因为没有电荷存储效应。其三,MOSFET 没有二次击穿失效机理,它在温度越高时往往耐力越强,而且发生热击穿的可能性越低。它们还可以在较宽的温度范围内提供较好的性能。此外,MOSFET 具有并行工作能力,具有正的电阻温度系数。温度较高的器件往往把电流导向其它MOSFET,允许并行电路配置。而且,MOSFET 的漏极和源极之间形成的寄生二极管可以充当箝位二极管,在电感性负载开关中特别有用。 场管有两种工作模式,即开关模式或线性模式。所谓开关模式,就是器件充当一个简单的开关,在开与关两个状态之间切换。线性工作模式是指器件工作在某个特性曲线中的线性部分,但也未必如此。此处的“线性”是指 MOSFET 保持连续性的工作状态,此时漏电流是所施加在栅极和源极之间电压的函数。它的线性工作模式与开关工作模式之间的区别是,在开关电路中,MOSFET 的漏电流是由外部元件确定的,而在线性电路设计中却并非如此。 2、运放所传递和处理的信号,包括直流信号、交流信号,以及交、直流叠加在一起的合成信号。而且该信号是按“比例(有符号+或-,如:同相比例或反相比例)”进行的。不一定全是“放大”,某些场合也可能是衰减(如:比例系数或传递函数 K=Vo/Vi=-1/10)。 运放直流指标有输入失调电压、输入失调电压的温度漂移(简称输入失调电压温漂)、输入偏置电流、输入失调电流、输入失调电流温漂、差模开环直流电压增益、共模抑制比、电源电压抑制比、输出峰-峰值电压、最大共模输入电压、最大差模输入电压。 交流指标有开环带宽、单位增益带宽、转换速率SR、全功率带宽、建立时间、等效输入噪声电压、差模输入阻抗、共模输入阻抗、输出阻抗。 个人认为,选择运放,可以只侧重考虑三个参数:输入偏置电流、供电电源和单位增益带宽。

    时间:2018-05-09 关键词: 电子电路 模电

  • 做了整整八年模电,却说自己根本没入门,怎么回事?

     从复旦攻读微电子专业模拟芯片设计方向研究生开始到现在,加上五年工作经验,已经整整八年了,其间聆听过很多国内外专家的指点。最近,应朋友之邀,写一点心得体会和大家共享。 我记得本科刚毕业时,由于本人打算研究传感器的,后来阴差阳错进了复旦逸夫楼专用集成电路与系统国家重点实验室做研究生。现在想来这个实验室名字大有深意,只是当时惘然。 电路和系统,看上去是两个概念, 两个层次。我同学有读电子学与信息系统方向研究生的,那时候知道他们是“系统”的, 而我们呢,是做模拟“电路”设计的,自然要偏向电路。而模拟芯片设计初学者对奇思*巧的电路总是很崇拜,尤其是这个领域的最权威的杂志JSSC (IEEE Journal of solid state circuits), 以前非常喜欢看, 当时立志看完近二十年的文章,打通奇经八脉,总是憧憬啥时候咱也灌水一篇, 那时候国内在此杂志发的文章凤毛麟角, 就是在国外读博士,能够在上面发一篇也属优秀了。 读研时,我导师是郑增钰教授,李联老师当时已经退休,逸夫楼邀请李老师每个礼拜过来指导。郑老师治学严谨,女中豪杰。李老师在模拟电路方面属于国内先驱人物,现在在很多公司被聘请为专家或顾问。李老师在87 年写的一本(运算放大器设计);即使现在看来也是经典之作。李老师和郑老师是同班同学,所以很要好,我自然相对于我同学能够幸运地得到李老师的指点。 李老师和郑老师给我的培养方案是:先从运算放大器学起。所以我记得我刚开始从小电流源开始设计。那时候感觉设计就是靠仿真调整参数。但是我却永远记住了李老师语重心长的话:运放是基础,运放设计弄好了,其他的也就容易了。当时不大理解,我同学的课题都是AD/DA,锁相环等“高端”的东东,而李老师和郑老师却要我做“原始”的模块,我仅有的在(固体电子学) (国内的垃圾杂志)发过的一篇论文就是轨到轨(rail-to-rail)放大器。做的过程中很郁闷,非常羡慕我同学的项目,但是感觉李老师和郑老师讲的总有他们道理,所以我就专门看JSSC 运放方面的文章,基本上近20 多年的全看了。 当时以为很懂这个了,后来工作后才发现其实还没懂。所谓懂,是要真正融会贯通,否则塞在脑袋里的知识再多,也是死的。但是运算放大器是模拟电路的基石,只有根基扎实方能枝繁叶茂,两位老师的良苦用心工作以后才明白。总的来说,在复旦,我感触最深的就是郑老师的严谨治学之风和李老师的这句话。 硕士毕业去找工作,当时有几个offer。我师兄孙立平, 李老师的关门弟子,推荐我去新涛科技,他说里面有个常仲元,鲁汶天主教大学博士,很厉害。我听从师兄建议就去了。新涛当时已经被IDT 以8500 万美金收购了,成为国内第一家成功的芯片公司。面试我的是公司创始人之一的总经理Howard. C. Yang(杨崇和)。Howard 是Oregon State University 的博士,锁相环专家。面试时他当时要我画了一个两级放大器带Miller 补偿的, 我很熟练。他说里面有个零点,我很奇怪,从没听过,云里雾里,后来才知道这个是Howard 在国际上首先提出来的, 等效模型中有个电阻,他自己命名为杨氏电阻。当时出于礼貌,不断点头。不过他们还是很满意,反正就这样进去了。我呢,面试唯一的遗憾是没见到常仲元, 大概他出差了。 进入新涛后,下了决心准备术业有专攻。因为本科和研究生时喜欢物理,数学和哲学,花了些精力在这些上面。工作后就得真刀真*的干了。每天上班仿真之余和下班后,就狂看英文原版书。第一本就是现在流行的Razavi 的那本书。读了三遍。感觉大有收获。那时候在新涛,初生牛犊不怕虎,应该来说,我还是做得很出色的,因此得到常总的赏识,被他评价为公司内最有potential的人。偶尔常总会过来指点一把,别人很羡慕。 其实我就记住了常总有次聊天时给我讲的心得, 他大意是说做模拟电路设计有三个境界:第一是会手算,意思是说pensile-to-paper, 电路其实应该手算的,仿真只是证明手算的结果。第二是,算后要思考,把电路变成一个直观的东西。第三就是创造电路。我大体上按照这三部曲进行的。 Razavi 的那本书后面的习题我仔细算了。公司的项目中,我也力图首先以手算为主, 放大器的那些参数,都是首先计算再和仿真结果对比。久而久之,我手计算的能力大大提高,一些小信号分析计算,感觉非常顺手。这里讲一个小插曲,有一次在一个项目中,一个保护回路AC 仿真总不稳定, 调来调去,总不行,这儿加电容,那儿加电阻,试了几下都不行,就找常总了。因为这个回路很大,所以感觉是瞎子摸象。常总一过来三下五除二就摆平了, 他仔细看了,然后就导出一个公式,找出了主极点和带宽表达式。通过这件事,我对常总佩服得五体投地, 同时也知道直观的威力。所以后来看书时,都会仔细推导书中的公式,然后再直观思考信号流, 不直观不罢手。 一年多下来, 对放大器终于能够透彻理解了,感觉学通了, 通之后发现一通百通。最后总结:放大器有两个难点,一个是频率响应,一个是反馈。其实所谓电路直观,就是用从反馈的角度来思考电路。每次分析了一些书上或者JSSC 上的“怪异”电路后,都会感叹:反馈呀,反馈!然后把分析的心得写在paper 上面。学通一个领域后再学其他相关领域会有某种“加速”作用。常总的方式是每次做一个新项目时,让下面人先研究研究。我在离开新涛前,做了一个锁相环。我以前没做过,然后就把我同学的硕士论文,以及书和很多paper 弄来研究,研究了一个半月,常总过来问我:锁相环的3dB 带宽弄懂了吧? 我笑答:早就弄懂了。我强大的运放的频率响应知识用在锁相环上,小菜了。我这时已经去研究高深的相位噪声和jitter 了。之后不久,一份30 多页的英文研究报告发出来,常总大加赞赏!。 后来在COMMIT时,有个项目是修改一个RF Transceiver 芯片, 使之从WCDMA 到TD-SCDMA。里面有个基带模拟滤波器。我以前从没接触过滤波器,就花了两个月时间,看了三本英文原版书,第一本有900 多页,和N 多paper, 一下子对整个滤波器领域,开关电容的,GmC 的,Active RC 的都懂了。提出修改方案时, 由于我运放根基扎实,看文章时对于滤波器信号流很容易懂,所以很短时间就能一个人提出芯片电路原理分析和修改方案。最后报告写出来(也是我的又一个得意之作),送给TI. TI 那边对这边一下子肃然起敬,Conference call 时, 他们首先说这份报告是“Great job!”,我英文没听懂,Julian 对我夸大拇指,说“他们对你评价很高呢”。 后来去Dallas, TI 那边对我们很尊敬, 我做报告时,很多人来听。总之,现在知道,凡事情,基础很重要,基础扎实学其他的很容易切入, 并且越学越快。我是02 年11 月去的COMMIT,当时面试我的也是我现在公司老板Julian。Julian 问我:你觉得SOC (system on chip)设计的环节在哪儿? 我说:应该是模拟电路吧,这个比较难一些。Julian 说错了,是系统。 我当时很不以为然, 觉得模拟电路工程师应该花精力在分析和设计电路上。 Julian 后来自己run了现在这公司On-Bright,把我也带来, 同时也从TI 拉了两个,有一个是方博士。我呢,给Julian 推荐了朱博士。这一两年,我和朱博士对方博士佩服得五体投地。方博士是TI 华人里面的顶级高手,做产品能力超强。On-Bright 现在做电源芯片,我和朱博士做了近两年,知道了系统的重要性。芯片设计最终一定要走向系统,这个是芯片设计的第四重境界。 电路如同砖瓦,系统如同大厦。芯片设计工程师一定要从系统角度考虑问题,否则就是只见树木,不见森林。电源芯片中,放大器,比较器都是最最普通的, 其难点在于对系统的透彻理解。在On-Bright,我真正见识了做产品,从定义到设计,再到debug, 芯片测试和系统测试,最后到RTP (release to production)。Julian 把TI 的先进产品开发流程和项目管理方式引入On-Bright,我和朱博士算是大开眼界,也知道了做产品的艰辛。

    时间:2018-05-09 关键词: 芯片 模电

  • pspice学习笔记——电路模拟过程

     电路模拟基本过程: 1,新建设计项目; 2,电路图生成; 3,电路特性分析类型和分析参数设置; 可在capture环境下新建或修改profile设置重新进行模拟。模拟类型分组设置结果存放在以SIM为扩展名的文件中。 4,运行pspice A/D程序,对电路进行模拟分析; 5,模拟结果的显示和分析; 完成电路模拟分析后,pspice按照电路特性分析的类型分别将计算结果存入扩展名为out的ASCII码输出文件以及扩展名为DAT的二进制文件中。 6,电路设计优化; 优化模块(optimizer)可以调用。 7,设计修正 电路设计出现问题或者分析参数出现问题,可能需要多次循环才可以满足要求。OUT文件中存放有错误信息。也可以灾波形显示分析窗口(probe)分析错误信息。 8,设计结果输出 得到符合要求的电路设计后,就可以调用capture输出全套电路图纸,包括各种统计报表。也可以根据需要将电路设计图数据传送给Orcad/layout,继续进行印刷电路板设计。

    时间:2018-05-09 关键词: 模拟电路 pspice

  • 关于字符串的逆序题目

    今天做了一道关于字符串逆序的题目,题目为输入I am a student,而输出为student a am I。这道题的思路很清楚,就是先把整个句子逆序,然后将一个一个单词逆序,这样便得到了最终结果。而在将单词逆序的时候,可以设置两个指针,一个指针依次遍历,当遍历到空格的时候,将此时的指针代表的值赋\0,然后将另一个指针传到逆序函数中,函数结束后,再赋空格。 /**********************************************************  File Name:           Description:   输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。(笔试题)                  句子中单词以空格符隔开。为简单起见,没有标点符号。                  例如输入“I am a student”,则输出“student a am I”  Fuction List:  ************************************************************/   #include <stdio.h>   #define N 50      void overturn1(char *p)   {       char *h = p;              while (*(++p) != '\0')       {           ;       }       p = p - 1;       char temp;       while(h <= p)       {           temp = *(p);           *p = *h;           *(h) = temp;               p--;           h++;       }   }      void overturn2(char* p)   {       char *pre = p;       char *cur = p;              while (*(cur) != '\0')       {           if (*(cur) == ' ')           {               *cur = '\0';               overturn1(pre);               *cur = ' ';               cur++;               pre = cur;           }           else           {               cur++;           }       }              overturn1(pre);          }      int main()   {       char phrase[N] = {0};              printf("please input:\n");       gets(phrase);              overturn1(phrase);       overturn2(phrase);       puts(phrase);              return 0;   }  

    时间:2018-05-07 关键词: C语言

  • C语言蓝桥杯题目两道

      Description:  密码发生器       在对银行账户等重要权限设置密码的时候,我们常常遇到这样的烦恼:如果为了好记用生日吧,容易被破解,不安全;如果设置不好记的密码,又担心自己也会忘记;如果写在纸上,担心纸张被别人发现或弄丢了...       这个程序的任务就是把一串拼音字母转换为6位数字(密码)。我们可以使用任何好记的拼音串(比如名字,王喜明,就写:wangximing)作为输入,程序输出6位数字。     变换的过程如下:       第一步. 把字符串6个一组折叠起来,比如wangximing则变为:       wangxi     ming        第二步. 把所有垂直在同一个位置的字符的ascii码值相加,得出6个数字,如上面的例子,则得出:       228 202 220 206 120 105       第三步. 再把每个数字“缩位”处理:就是把每个位的数字相加,得出的数字如果不是一位数字,就再缩位,直到变成一位数字为止。例如: 228 => 2+2+8=12 => 1+2=3     上面的数字缩位后变为:344836, 这就是程序最终的输出结果!     要求程序从标准输入接收数据,在标准输出上输出结果。       输入格式为:第一行是一个整数n(<100),表示下边有多少输入行,接下来是n行字符串,就是等待变换的字符串。       输出格式为:n行变换后的6位密码。       例如,输入: 5               zhangfeng wangximing               jiujingfazi               woaibeijingtiananmen haohaoxuexi                   则输出: 772243 344836 297332 716652 875843   Fuction List:  ************************************************************/   #include <stdio.h>   #include <string.h>      int reduction(int m)   {       int k = 0;       if (m<10)       {           return m;       }       while(m)       {           k += m%10;           m /= 10;       }              return reduction(k);   }      void f(char s[],char x[])   {       int i,j,n,k;       n = strlen(s);       for (i=0; i<6; i++)       {           for (k=0,j=i; j<n; j+=6)           {               k += s[j];           }           x[i] = reduction(k) + '0';       }       x[6] = '\n';   }      int main()   {       char s[100] = {"wangximing"};       char t[10000] = {""};       int len = 0;       int n;        // 有n行密码              scanf ("%d",&n);              while (n)       {           n--;           scanf ("%s",s);           f(s,t+len);           len += 7;       }       t[len-1] = '\0';              puts(t);                  return 0;    }   [cpp] view plain copy       /**********************************************************  File Name:           Description:   4.取球游戏                                 今盒子里有n个小球,A、B两人轮流从盒中取球,                  每个人都可以看到另一个人取了多少个,也可以                  看到盒中还剩下多少个,并且两人都很聪明,不                  会做出错误的判断。                       我们约定:                            每个人从盒子中取出的球的数目必须是:1,3,7或者8个。                       轮到某一方取球时不能弃权!                        A先取球,然后双方交替取球,直到取完。                        被迫拿到最后一个球的一方为负方(输方)                        请编程确定出在双方都不判断失误的情况下,对于特定的初始球数,A是否能赢?      Fuction List:  ************************************************************/   #include <stdio.h>      int main()   {       int a[16] = {0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1};       int n[10001];       int j[10001];       int m;       int i;       int temp;              scanf("%d",&m);       for(i = 0; i < m; i++)       {           scanf("%d",&n[i]);           temp = n[i] % 15;           j[i] = a[temp-1];       }              for (i = 0; i < m; i++)       {           printf("%d\n",j[i]);       }              return 0;   }  

    时间:2018-05-07 关键词: C语言

  • 嵌入式C语言阶段性总结

     最近做完了聊天室的项目,C语言基础的学习阶段也算是告一段落了,但我对C语言还是只是一个入门,就像一个工具,我现在只是了解、会初步使用它了,但并没有达到如臂挥使的地步,今后还需要对C语言进行更深入的学习,今天我就讲一下我个人学到现在对C语言的认识,自己的理解,若有错误,还望指出,不甚感激。 首先是C语言整体的脉络,C语言包括哪些东西?哪些部分重要,哪些部分需要着重理解?这是我经常问自己的两个问题。 首先,C语言有哪些东西?C语言的东西其实并不是太多: 1、最基本的一些数据类型及其所占的内存大小、还有一些基础的计算机常识(进制转换等)。这些东西在对C语言有了一定的了解后都是比较容易的。 2、三种逻辑(顺序、选择和循环) 顺序语句就是从上到下没有判断,一步到底的语句。 选择语句就是if和switch语句,在特定的场合,switch语句使用会使程序看的简单明了,尤其是选择情况较多的时候。大多数时候if语句用的多一点。 循环语句就是while、do...while、和for语句,这三个do...while我用的不是太多,就不做评价了。while语句和for语句用的场合非常多,我说一下几个注意点: a、在多层循环中,尽可能把最长的循环放在最外围,节省cpu的资源。 b、不能在循环体内修改循环变量,防止循环失控。 c、循环尽可能的短,太多行的循环代码会大大的影响阅读。解决方法:使用子函数处理 d、把循环嵌套控制在3层以内,超过三层,对代码的理解难度大大增加了。 e、for语句的控制表达式不能包含任何浮点类型的对象 还有就是break和continue语句,经常会有人问我,这两个关键字跳出的是什么,break是跳出离它最 近的一个循环(switch中的是跳出switch,不是跳出循环),continue也是一样。 3、数组和指针 数组我把它分为两个:普通数组和字符串,其中对字符串的操作就是C语言考核的最关键的一步,因为其涉及到了数组和指针,把字符串操作的很牛的人,他们对指针的理解一定很深。 普通数组:一维数组的内容不是太多,但是对算法的要求比较高,最基础的是一定要会冒泡和选择排序,这两种算法最基础,但也是在排序上用的最多的(如果会更好的算法的话另外谈),有兴趣的话可以对算法这一块深入研究(我还没有来得及研究算法,不是太懂)。二维数组的话,要理解其内存的分配情况,元素的存放顺序,会对二维数组进行输入输出,其他的,就是算法了,以后慢慢去研究去。 字符串:字符串这部分的话,因为C语言对字符串的操作不像c++那么容易,所以,要对指针和数组这块下一点功夫。最简单的就是将string.h库函数中的strlen,strcmp,strcpy,strcat,strncmp这几个函数自己实现一篇,使用指针的方式。 指针:当初老师讲指针的时候,说了一句,如果你把指针学会了,C语言你就掌握了70%。当初我还有些怀疑,现在的我十分赞同这句话。指针就是C语言的精华所在,C语言是一门软硬通吃的语言,归根到底就是这个指针的知识,他能直接操控底层。关于指针,我印象最深的是那个例子,*p 把p想象成小明家的门牌号,而*则是一把万能钥匙,*p代表的值就是小明本人,你可以把小明家的地址给别人,别人拿着*(万能钥匙)和p(小明家门牌号)就能去看小明本人,可以对小明本人进行操作。这个例子当时给我的印象非常深,那以后我感觉我对指针的理解顿时清楚了很多,非常感谢我的C语言启蒙老师,有了他的带领,我才能对C语言有深一步的理解。 4、函数 函数的话我感觉就是main函数的一个延伸,但它是可重复调用的,你可以把一些繁杂的步骤写到一个函数里面,这样main函数才不会显得太臃肿。函数最重要的就是它的格式,返回值+函数名+(形参)+函数体,其中有一个容易忽视的点就是,当形参是一个指针的时候,在函数开始时,要进行入口参数检查,就是对指针是否为空进行判断,要不然容易出现段错误。还有一个就是,如果一个函数在main函数下面,最好进行一下声明,虽说现在的编译器已经帮我们省略了这一步骤,但我们还是要养成良好的习惯,毕竟如果给你一个旧版本的编译器的话,就会报错。编译器的优化并不是我们偷懒的理由。不过我们也可以将函数写在main函数上方,这样的话就不需要考虑这个问题了。在函数这一部分,我在有些地方还是有很多不足之处,比如说递归函数和回调函数,递归函数涉及到一些算法,本人对算法还是不够了解,所以。。。还有回调函数接触的不多,到现在为止就接触到了一个sqlite3中的sqlite3_exec函数第三个参数为函数名的这种情况。 5、关键字 Static全局变量  作用范围局限于源文件,不可被源文件的其他文件使用 局部变量  局限于特定函数,出作用域不释放,函数结束后依然存在 函数   作用范围局限于源文件,不可被源文件的其他文件使用 函数名在其它文件不可见 const 这个关键字其实只要记住一局诗就行了,“近水楼台先得月”,最靠近const的那个就是不能变的。 extern 这个关键字一般用在.h头文件中,声明函数,不是本文件中的。 struct 结构体,包含多种数据类型的变量,在数据结构中经常用到,比如说:链表、栈和队列等 enum 枚举 和define的功能差不多,枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。 union 共用体,和结构体的结构差不多,但是结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员,一般用来测试系统的大小端。 到这边,C语言的内容其实就差不多了,除了其他一些零零碎碎的小知识点。但我学的是LinuxC,所以我还学了Linux进程和线程的管理,还有文件的操作以及Linux网络的编程。Linux网络的编程---搭建tcp协议的服务器 实际上就是那几个套路前两篇文章中的tcp_net_socket.c中已经整理好了。文件操作的话,Linux系统提供了一个文件描述符的机制,open之后对文件描述符进行read和write,而C语言则是fopen函数,返回一个指针,原理差不多,只不过C语言提供的fopen系列函数可以跨平台操作。线程和进程的话其实也没有多少内容(可能学的比较浅的缘故),太复杂的内容也没有接触到。线程中有一个东西要提一下,那就是同步和互斥的关系。其实也很好理解,打个比方,一扇门规定只能进一个人,这个时候来了10个人,如果没有秩序,一拥而上的话就是互斥,如果他们按照一定的规矩进行排队的话,那就是同步了,同步就是一种特殊的互斥。 想想应该差不多了,如果有漏掉了什么重要的东西的话,还希望读者能提示我一下,方便我改进改进。

    时间:2018-05-07 关键词: 嵌入式 C语言

  • 嵌入式系统学习——STM32之GPIO

     STM32库函数说明及示例(版本V1.4.0) ----第一篇:GPIO库 文档说明和约定: 该文档主要是对STM32F4各个模块的库进行翻译和说明。文档中加入了作者的一些理解,建议和小贴士。并且在文档最后,加入了一些使用该库模块的案例。希望大家通过对该文档的阅读,可以更好的使用STM32的库函数进行学习和项目开发。之所以选用1.4.0版本进行翻译和说明,因为该版本群众基础较好,有大量的使用者和相关资料。后续也会推出新版本库和CubeMX库的翻译和说明,希望大家喜欢和支持。如果大家觉得文档有什么问题,麻烦请提出,如果确认问题存在,作者会及时修改。 相关术语说明: gpio:通用输入输出接口 gpio管脚:一个io管脚,这个管脚可以有多个配置。在库函数中用GPIO_Pin_1这样的宏定义表示 gpio端口(gpio分组):一组gpio管脚的信息。在库函数中用宏定义GPIOA GPIOB等表示 1 gpio库说明 库文件名:stm32f4xx_gpio.c 文档提示翻译: 如何使用这个驱动 (1) 使用RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOx, ENABLE)函数使能GPIO的AHB总线时钟。 (2) 使用GPIO_Init()函数对每个引脚进行四种可能的配置 《1》 输入状态:Floating(浮空), Pull-up(上拉), Pull-down(下拉) 《2》 输出状态:Push-Pull (上拉下拉)(Pull-up(上拉), Pull-down(下拉) or no Pull(不上拉也不下拉)),Open Drain(开漏) (Pull-up(上拉), Pull-down(下拉) or no Pull(不上拉也不下拉)),在输出模式,速度配置成2MHZ,25MHZ,50MHZ和100MHZ. 《3》 第二功能:上拉下拉和开漏 《4》 模拟:当一个管脚被用作ADC通道或者DAC输出的时候,需要配置成此模式 (3) 外设的第二功能: 《1》 在ADC和DAC模式,使用GPIO_InitStruct->GPIO_Mode = GPIO_Mode_AN把需要的管脚配置成模拟模式 《2》 对于其它的管脚(定时器,串口等): l 使用GPIO_PinAFConfig()函数把管脚和需要的第二功能进行连接 l 使用GPIO_InitStruct->GPIO_Mode = GPIO_Mode_AF把需要的管脚配置成第二功能模式 l 通过成员变量GPIO_PuPd, GPIO_OType and GPIO_Speed选择类型,上拉下拉和输出速度 l 调用函数GPIO_Init() (4) 在输入模式,使用函数GPIO_ReadInputDataBit()得到配置好管脚的电平 (5) 在输出模式,使用函数GPIO_SetBits()/GPIO_ResetBits()设置配置好IO的高低电平 (6) 在复位过程和刚刚复位后,第二功能是无效的,GPIO被配置成了输入浮空模式(JTAG管脚除外) (7) 当LSE振荡器关闭的时候,LSE振荡器管脚OSC32_IN和OSC32_OUT可以作为通过IO来使用(分别用PC14和PC15表示)。LSE的优先级高于GPIO函数 (8) 当HSE振荡器关闭的时候,HSE振荡器管脚OSC_IN和OSC_OUT可以作为通用IO(PH0,PH1)来使用。HSE的优先级高于GPIO函数。 2 具体函数说明 初始化和配置相关函数 1. void GPIO_DeInit(GPIO_TypeDef* GPIOx) 函数解释:gpio的反初始化函数,该函数的作用是把GPIO相关的寄存器配置成上电复位后的默认状态,在第一次初始化前或者不再使用某一个接口后可以调用该函数。 函数参数说明:GPIOx:gpio的分组,如GPIOA GPIOB GPIOC等的宏定义(这些宏定义在头文件stm32f4xx.h中,由厂家写好,我们直接使用即可) 2. void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef*GPIO_InitStruct) 函数解释:gpio的初始化函数,该函数的作用是对io进行初始化。 函数参数说明:(1)GPIOx:gpio的分组,如GPIOA GPIOB GPIOC等的宏定义。 (2)GPIO_InitStruct:gpio的初始化相关结构体。该结构体里面的成员变量决定了我们具体的初始化参数。以下进行说明: l GPIO_Pin:指定具体的IO脚,如GPIO_Pin_0 GPIO_Pin_1这样的宏定义,这些宏由厂家写好,我们直接使用即可。 l GPIO_Mode:指定gpio的模式,有以下四种模式: GPIO_Mode_IN(输入),GPIO_Mode_OUT(输出),GPIO_Mode_AF(第二功能),GPIO_Mode_AN(模拟),可以直接使用这四种宏定义。 l GPIO_Speed:指定IO的最快翻转速度,也就是当使用IO产生频率(如PWM)的最快速度。有以下四种速度的配置: GPIO_Low_Speed (低速),GPIO_Medium_Speed(中等速度),GPIO_Fast_Speed(快速),GPIO_High_Speed(高速),可以直接使用这四种宏定义。 l GPIO_OType:指定选择管脚的输出类型,有以下两种配置: GPIO_OType_PP (推挽方式输出),GPIO_OType_OD(开漏方式输出),可以直接使用这两种宏定义。 Tips: 推挽输出:推挽输出就是单片机引脚可以直接输出高电平电压。低电平时接地,高电平时输出单片机电源电压。这种方式可以不接上拉电阻。但如果输出端可能会接地的话,这个时候输出高电平可能引发单片机运行不稳定,甚至可能烧坏引脚。推挽方式的驱动力更大。 开漏输出:开漏输出就是不输出电压,低电平时接地,高电平时不接地。如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机电压低的时候。 l GPIO_PuPd。指定选择管脚的上拉和下拉模式。有如下三种配置: GPIO_PuPd_NOPULL(不上拉也不下拉),GPIO_PuPd_UP(上拉),GPIO_PuPd_DOWN(下拉)。Tips:这些都是IO的内部上拉或者下拉模式,也可以接上拉和下拉电阻通过硬件进行外部上拉和外部下拉。 3. void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct) 函数解释:gpio结构体的初始化。对GPIO_InitStruct结构体进行默认配置 函数参数说明:GPIO_InitStruct,直接传入该结构体的指针,在该函数内会对结构体进行初始化。 4. void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 函数解释:锁定gpio的寄存器,锁定的寄存器是GPIOx_MODER,GPIOx_OTYPER, GPIOx_OSPEEDR,GPIOx_PUPDR, GPIOx_AFRL and GPIOx_AFRH。在下一次复位前,被锁定的管脚不能被修改。 函数参数说明:GPIOx:gpio的分组(如GPIOA,GPIOB等)。GPIO_Pin:具体的gpio管脚(如GPIO_Pin_0 GPIO_Pin_1这样的宏定义) GPIO的读写函数 1. uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_tGPIO_Pin) 函数解释:读取io输入管脚的值 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_Pin:具体的gpio管脚 函数返回值说明:输入管脚的值Bit_SET(高电平) Bit_RESET(低电平) 2. uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx) 函数解释:读取输入io数据,该函数用于读取一个IO分组的所有数据 函数参数说明:GPIOx:gpio的分组/gpio端口 函数返回值说明:一个io端口的所有数据 (输入状态) 3. uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_tGPIO_Pin) 函数解释:读取io输出管脚的值 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_Pin:具体的gpio管脚 函数返回值说明:输出管脚的值Bit_SET(高电平) Bit_RESET(低电平) 4. uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx) 函数解释:读取输出io分组/端口的值 函数参数说明:GPIOx:gpio的分组/gpio端口 函数返回值说明:一个io端口的所有数据 (输出状态) 5. void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 函数解释:对io管脚进行置位(输出高电平)。这个函数使用GPIOx_BSRR寄存器来实现原子读或者修改操作。在这种情况下,在读和修改访问时发生一个IRQ中断是没有危险的。 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_Pin:具体的gpio管脚或者是io管脚的组合 6. void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 函数解释:对io管脚进行复位(输出低电平)。这个函数使用GPIOx_BSRR寄存器来实现原子读或者修改操作。在这种情况下,在读和修改访问时发生一个IRQ中断是没有危险的。 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_Pin:具体的gpio管脚或者是io管脚的组合 7. void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitActionBitVal) 函数解释:对某一位进行写入操作 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_Pin:具体的gpio管脚;BitVal:写入高电平或者低电平(Bit_RESET:写入低电平 Bit_SET:写入高电平) 8. void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal) 函数解释:对gpio端口进行写入操作,适用于对统一端口的多个管脚的写入 函数参数说明:GPIOx:gpio的分组/gpio端口; BitVal:写入高电平或者低电平(Bit_RESET:写入低电平Bit_SET:写入高电平) 9. void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 函数解释:翻转指定的gpio口,也就是说,如果当前的io是低电平,则变成高电平,如果当前io是高电平,则变成低电平 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_Pin:具体的gpio管脚。 Gpio复用功能配置函数 1. void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource,uint8_t GPIO_AF) 函数解释:改变指定管脚的映射关系。即配置指定管脚的复用功能。 函数参数说明:GPIOx:gpio的分组/gpio端口;GPIO_PinSource:具体要配置成复用功能的管脚(如GPIO_Pin_0 GPIO_Pin_1这样的宏定义);GPIO_AF:选择该管脚要使用的复用功能。有如下配置:(注意:复用功能的配置要和实际管脚支持的复用功能匹配) GPIO_AF_RTC_50Hz: Connect RTC_50Hz pin toAF0 (default after reset) GPIO_AF_MCO: Connect MCO pin (MCO1 and MCO2)to AF0 (default after reset) GPIO_AF_TAMPER: Connect TAMPER pins(TAMPER_1 and TAMPER_2) to AF0 (default after reset) GPIO_AF_SWJ: Connect SWJ pins (SWD andJTAG)to AF0 (default after reset) GPIO_AF_TRACE: Connect TRACE pins to AF0(default after reset) GPIO_AF_TIM1: Connect TIM1 pins to AF1 GPIO_AF_TIM2: Connect TIM2 pins to AF1 GPIO_AF_TIM3: Connect TIM3 pins to AF2 GPIO_AF_TIM4: Connect TIM4 pins to AF2 GPIO_AF_TIM5: Connect TIM5 pins to AF2 GPIO_AF_TIM8: Connect TIM8 pins to AF3 GPIO_AF_TIM9: Connect TIM9 pins to AF3 GPIO_AF_TIM10: Connect TIM10 pins to AF3 GPIO_AF_TIM11: Connect TIM11 pins to AF3 GPIO_AF_I2C1: Connect I2C1 pins to AF4 GPIO_AF_I2C2: Connect I2C2 pins to AF4 GPIO_AF_I2C3: Connect I2C3 pins to AF4 GPIO_AF_SPI1: Connect SPI1 pins to AF5 GPIO_AF_SPI2: Connect SPI2/I2S2 pins to AF5 GPIO_AF_SPI4: Connect SPI4 pins to AF5 GPIO_AF_SPI5: Connect SPI5 pins to AF5 GPIO_AF_SPI6: Connect SPI6 pins to AF5 GPIO_AF_SAI1: Connect SAI1 pins to AF6 forSTM32F42xxx/43xxx devices. GPIO_AF_SPI3: Connect SPI3/I2S3 pins to AF6 GPIO_AF_I2S3ext: Connect I2S3ext pins toAF7 GPIO_AF_USART1: Connect USART1 pins to AF7 GPIO_AF_USART2: Connect USART2 pins to AF7 GPIO_AF_USART3: Connect USART3 pins to AF7 GPIO_AF_UART4: Connect UART4 pins to AF8 GPIO_AF_UART5: Connect UART5 pins to AF8 GPIO_AF_USART6: Connect USART6 pins to AF8 GPIO_AF_UART7: Connect UART7 pins to AF8 GPIO_AF_UART8: Connect UART8 pins to AF8 GPIO_AF_CAN1: Connect CAN1 pins to AF9 GPIO_AF_CAN2: Connect CAN2 pins to AF9 GPIO_AF_TIM12: Connect TIM12 pins to AF9 GPIO_AF_TIM13: Connect TIM13 pins to AF9 GPIO_AF_TIM14: Connect TIM14 pins to AF9 GPIO_AF_OTG_FS: Connect OTG_FS pins to AF10 GPIO_AF_OTG_HS: Connect OTG_HS pins to AF10 GPIO_AF_ETH: Connect ETHERNET pins to AF11 GPIO_AF_FSMC: Connect FSMC pins to AF12 GPIO_AF_FMC: Connect FMC pins to AF12 forSTM32F42xxx/43xxx devices. GPIO_AF_OTG_HS_FS: Connect OTG HS(configured in FS) pins to AF12 GPIO_AF_SDIO: Connect SDIO pins to AF12 GPIO_AF_DCMI: Connect DCMI pins to AF13 GPIO_AF_LTDC: Connect LTDC pins to AF14 forSTM32F429xx/439xx devices. GPIO_AF_EVENTOUT: Connect EVENTOUT pins toAF15 代码示例: 示例一:把gpioa6配置成输出管脚,并配置成高电平 GPIO_InitTypeDefGPIO_InitStruct; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); GPIO_InitStruct.GPIO_Pin= GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode= GPIO_Mode_OUT; GPIO_InitStruct.GPIO_PuPd= GPIO_PuPd_UP; GPIO_Init(GPIOA,&GPIO_InitStruct); GPIO_SetBits(GPIOA,GPIO_Pin_6); 示例二:把gpioe4配置成输入 GPIO_InitTypeDefGPIO_InitStruct; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE); GPIO_InitStruct.GPIO_Pin= GPIO_Pin_4; GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IN; GPIO_InitStruct.GPIO_PuPd= GPIO_PuPd_UP; GPIO_Init(GPIOE,&GPIO_InitStruct); 示例三:配置复用功能 PA9 PA10 配置成串口1的收发接口 GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟 //串口1对应引脚复用映射 GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);//GPIOA9复用为USART1 GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//GPIOA10复用为USART1 //USART1端口配置 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能 GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz; //速度50MHz GPIO_InitStructure.GPIO_OType= GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化PA9,PA10

    时间:2018-05-07 关键词: 嵌入式 STM32 gpio

  • STM32之启动文件详解

     在嵌入式应用程序开发过程里,由于使用C语言编程,基本很少涉及到机器底层寄存器的执行过程,一般都会直接在main函数里开始写代码,似乎main成为了理所当然的起点,尽管从C程序的角度来看程序都是直接从main函数开始执行。然而,MCU上电后,是如何寻找到并执行main函数这一问题却很自然的被忽略了!事实上微控制器是无法从硬件上去定位main函数的入口地址,因为使用C语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,因此main函数的入口地址在编译后便不一定是一个绝对地址。MCU上电后又是如何寻找到这个入口地址呢?以前接触无论是PIC、AVR、MSP430或是51过程中都没涉及到启动文件的配置,仅仅只有熔丝位或配置字是需要根据实际使用配置来设置,其实并非没有,而是由于大部分的开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。然而,但接触到嵌入内核比如Linux系统移植过程“bootloader”却是很重要也是必不可少的一个环节。事实上,每一种微控制器,无论性能高下,结构简繁,价格贵贱都是必须有启动文件才能正常工作的,它的作用同“bootloader”类似。启动文件完成了微控制器从“复位”到“开始执行main函数”中间这段时间的必要启动配置。 在STM32中,如果是在MDK下创建一个工程,一般都有提示是否加入Star up Code文件,这个就是启动文件,这里有个误区,一般对于初学者来看,很容易误以为STM32F10x.s这个启动文件是STM32所有类型芯片的通用启动文件,因此也自然不会去理会它的作用,事实上,这个启动文件只是针对部分STM32系列,如果仔细看过它的启动代码就会发现里面很多中断函数定义是没有的,甚至有些和STM32F10x_it.c里的函数是有出路的,如果刚好用到了默认的这个中断服务子函数的话,程序一旦运行到了中断是找不到入口地址的,这样就会莫名其妙地不知问题所在。STM32F10x.s是MDK提供的启动代码,从其里面的内容看来,它只定义了3个串口,4个定时器。实际上STM32的系列产品有5个串口的型号,也只有有2个串口的型号,定时器也是,做多的有8个定时器。比如,如果你用的STM32F103ZET6,而启动文件用的是STM32F10x.s的话,你可以正常使用串口1~3的中断,而串口4和5的中断,则无法正常使用。所以STM32F10x.s并不能适用所有的STM32型号,对于不同型号的STM32,正确做法是选择不同的启动文件。ST公司提供了3个启动文件:startup_stm32f10x_ld.s /startup_stm32f10x_md.s/startup_stm32f10x_hd.s 分别适用于小容量/中容量/大容量的STM32芯片,具体判断方法如下: 小容量:FLASH≤32K 中容量:64K≤FLASH≤128K 大容量:256K≤FLASH 在启动代码中,补充几点: 启动代码中的两条语句解释: 一、PROC 为子程序开始,ENDP 为子程序结束 二、[weak] 的意思是该函数优先级比较弱,如果其它地方定义了一个同名函数,那么此处的这个函数就被取代了。语法格式为 EXPORT 标号 {[WEAK]} 。EXPORT 可用GLOBAL代替。 对于_main函数的理解: 事实上,_main 和main是两个完全不同的函数!_main代码是编译器自动创建的,因此无法找到_main代码。MDK文档中有一句说明:it is automatically craated by the linker when it sees a definition of main() .大体意思可以理解为:当编译器发现定义了main函数,那么就会自动创建_main. _main 和main的关系 _main 主要做两件事:其一,C所需的资源;其二,调用main函数。这就不难理解为什么在启动代码调用的是_main ,最后却能转到main函数中去执行的原因了。 AREA指令的理解 AREA指令是一个伪指令,用于段定义。ARM汇编程序由段组成,段是相对独立的指令或数据单位,每个段由AREA伪指令定义,并定义段的属性。 AREA参数说明: * STACK——AREA指令的一个参数,定义段名称 * NOINIT——AREA指令的一个参数,指定本数据段仅仅保留了内在单元,而将句初始值写入内存单元,此时内存单元值初始化为0 * READWRITE——指定本段为可读可写,数据段默认为READWRITE. READWRITE(读写)、READONLY(只读) * ALIGN——也是一个伪指令,指定对齐方式。ALIGN n 指令的对齐值有两种选择:n或者2^n 例子:开辟一个堆栈段,段名为STACK,定义为可读可写,将内存单元初始化为0,对齐方式为8字节对齐。 AREA STACK,NOINIT,READWRITE,ALIGN=3

    时间:2018-05-07 关键词: 单片机 STM32

发布文章