• 学单片机,到底在学什么?

     学单片机,到底在学什么那?我认为主要在学三样东西 一,各种外设 学习外设就必须学会看技术手册,我们常见的外设包括各种传感器,包括OLED等显示屏,包括UHF电子标签读写器等等,可以说外设是学不完的,你去看看现在很多无论是教学51的还是stm32,TI的msp430之类的教程,都会花很多时间在教外设上,比如老师会带大家读一读DS18B20的技术手册,看看相应的供电电压,性能参数之类。 二,通信协议 有了外设,那外设是如何与单片机通信的那?这又是一部分值得研究的内容,常见的通信协议有485,232,CAN总线,USART等等,你只有研究透了通信的协议,通信的时序你才有可能使能起来你的外设,否则你外设到主控之间的路都修不通,谈何使能一说? 以上两点是不设计单片机本身的,外设的属性是外设自身决定的,外设与主控之间的通信也是出厂设计时候就规定好的,不能说和51通信的时候是一种,和stm32的时候又是一种。 三,单片机本身 单片机本身就是指单片机的一些参数配置,这个每个单片机肯定是不相同的,那定时器来说吧,51单片机直接访问定时器及其,stm32一般要通过库函数,同时要考虑使用stm32的那个定时器,是基本定时器就能满足要求?还是要用到通用定时器,高级定时器?这一部分就是单片机的本身属性的问题了。 可以说只要是裸机程序,就肯定是在学习这三部分的内容。

    时间:2018-04-25 关键词: 单片机

  • 高手教你如何编写高效率稳定的单片机代码

     由于单片机的性能同电脑的性能是天渊之别的,无论从空间资源上、内存资源、工作频率,都是无法与之比较的。PC 机编程基本上不用考虑空间的占用、内存的占用的问题,最终目的就是实现功能就可以了。对于单片机来说就截然不同了,一般的单片机的Flash 和Ram 的资源是以KB 来衡量的,可想而知,单片机的资源是少得可怜,为此我们必须想法设法榨尽其所有资源,将它的性能发挥到最佳,程序设计时必须 遵循以下几点进行优化: 使用尽量小的数据类型 能用unsiged就不用signed; 能用char就不用int; 能不用floating就不用。 能用位操作不用算数。 使用自加、自减指令 通常使用自加、自减指令和复合赋值表达式(如a-=1 及a+=1 等)都能够生成高质量的 程序代码,编译器通常都能够生成inc 和dec 之类的指令,而使用a=a+1 或a=a-1 之类 的指令,有很多C 编译器都会生成二到三个字节的指令。 减少运算的强度 可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。 (1) 求余运算 N= N %8 可以改为N = N &7 说明:位操作只需一个指令周期即可完成,而大部分的C 编译器的“%”运算均是调用子程序来 完成,代码长、执行速度慢。通常,只要求是求2n 方的余数,均可使用位操作的方法来代替。 (2) 平方运算 N=Pow(3,2) 可以改为N=3*3 说明:在有内置硬件乘法器的单片机中(如51 系列),乘法运算比求平方运算快得多, 因为浮点数 的求平方是通过调用子程序来实现的,乘法运算的子程序比平方运算的子程序代码短,执行速度快。 (3) 用位移代替乘法除法 N=M*8 可以改为N=M<<3 N=M/8 可以改为N=M>>3 说明:通常如果需要乘以或除以2n,都可以用移位的方法代替。如果乘以2n,都可以生成左移 的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到代码比调用乘除法子 程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果。 如N=M*9可以改为N=(M<<3)+M; (4) 自加自减的区别 例如我们平时使用的延时函数都是通过采用自加的方式来实现。 void DelayNms(UINT16 t) { UINT16 i,j; for(i=0;i define MAX(A,B) {(A)>(B)?(A):(B)} 说明:函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函 数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语 句对当前栈进行检查;同时,cpu 也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以, 函数调用需要一些cpu 时间。而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序, 不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。 适当地使用算法 假如有一道算术题,求1~100 的和。 作为程序员的我们会毫不犹豫地点击键盘写出以下的计算方法: UINT16 Sum(void) { UINT8 i,s; for(i=1;i<=100;i++) { s+=i; } return s; } 很明显大家都会想到这种方法,但是效率方面并不如意,我们需要动脑筋,就是采用数学算法解决问题, 使计算效率提升一个级别。 UINT16 Sum(void) { UINT16 s; s=(100 *(100+1))>>1; return s; } 结果很明显,同样的结果不同的计算方法,运行效率会有大大不同,所以我们需要最大限度地通过数 学的方法提高程序的执行效率。 用指针代替数组 在许多种情况下,可以用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引相 比,指针一般能使代码速度更快,占用空间更少。使用多维数组时差异更明显。下面的代码作用是相同的, 但是效率不一样。 UINT8 szArrayA[64]; UINT8 szArrayB[64]; UINT8 i; UINT8 *p=szArray; for(i=0;i<64;i++)szArrayB[i]=szArrayA[i]; for(i=0;i<64;i++)szArrayB[i]=*p++; 指针方法的优点是,szArrayA 的地址装入指针p 后,在每次循环中只需对p 增量操作。在数组索引 方法中,每次循环中都必须进行基于i 值求数组下标的复杂运算。 强制转换 C 语言精髓第一精髓就是指针的使用,第二精髓就是强制转换的使用,恰当地利用指针和强制转换不但 可以提供程序效率,而且使程序更加之简洁,由于强制转换在C 语言编程中占有重要的地位,下面将已五 个比较典型的例子作为讲解。 例子1:将带符号字节整型转换为无符号字节整型 UINT8 a=0; INT8 b=-3; a=(UINT8)b; 例子2:在大端模式下(8051 系列单片机是大端模式),将数组a[2]转化为无符号16 位整型值。 方法1:采用位移方法。 UINT8 a[2]={0x12,0x34}; UINT16 b=0; b=(a[0]<<8)|a[1]; 结果:b=0x1234 方法2:强制类型转换。 UINT8 a[2]={0x12,0x34}; UINT16 b=0; b= (UINT16 )a; //强制转换 结果:b=0x1234 例子3:保存结构体数据内容。 方法1:逐个保存。 typedef struct _ST { UINT8 a; UINT8 b; UINT8 c; UINT8 d; UINT8 e; }ST; ST s; UINT8 a[5]={0}; s.a=1; s.b=2; s.c=3; s.d=4; s.e=5; a[0]=s.a; a[1]=s.b; a[2]=s.c; a[3]=s.d; a[4]=s.e; 结果:数组a 存储的内容是1、2、3、4、5。 方法2:强制类型转换。 typedef struct _ST { UINT8 a; UINT8 b; UINT8 c; UINT8 d; UINT8 e; }ST; ST s; UINT8 a[5]={0}; UINT8 p=(UINT8 )&s;//强制转换 UINT8 i=0; s.a=1; s.b=2; s.c=3; s.d=4; s.e=5; for(i=0;i define Perror(FUN) printf(“Err:%s %s %d: %s\n”, FILE, func,LINE,FUN) 类linux的perror函数实现,这里加了出错的文件位置,所在函数,引发出错调用的函数FUN。 宏中#和##的用法 define STR(s) #s define CONS(a, b) int(a##e##b) printf(STR(vck));//输出vck printf(“%d\n”, CONS(2,3));//2e3 输出2000

    时间:2018-04-25 关键词: 单片机代码 单片机

  • 初学单片机几个不易掌握的概念

     asp.net跳转页面的三种方法比较 1、 response.redirect 这个跳转页面的方法跳转的速度不快,因为它要走2个来回(2次postback),但他可以跳 转到任何页面,没有站点页面限制(即可以由雅虎跳到新浪),同时不能跳过登录保护。但速度慢是其最大缺陷!redirect跳转机制:首先是发送一个http请求到客户端,通知需要跳转到新页面,然后客户端在发送跳转请求到服务器端。需要注意的是跳转后内部空间保存的所有数据信息将会丢失,所以需要用到session。 2、 server.transfer 速度快,只需要一次postback ,但是。。。。他必须是在同一个站点下,因为它是server的一个方法。另外,他能跳过登录保护。你可以写个小程序试试:设计一个由页面一到页面二的跳转,但要进入到页面二需要登录,form认证,但如果跳转语句使用transfer的话,那就不会弹出登录页面了。这个方法的重定向请求是发生在服务器端,所以浏览器的url地址仍然保留的是原页面的地址! 3、 sever.execute 这个方法主要是用在页面设计上面,而且他必须是跳转同一站点下的页面。这个方法是需要将一个页面的输出结果插入到另一个aspx页面的时候使用,大部分是在表格中,将某一个页面类似于嵌套的方式存在于另一页面。 总结: 当需要把用户跳转到另一台服务器上的页面的时候 ,使用redirect 当需要把用户跳转到非aspx页面时候,如html,使用redirect 需要把查询字符串作为url一部分的时候保留传给服务器的时候,因为其他2种方法不能做到2次postback,把数据先带回服务器,使用redirect 需要aspx页面间的转换(不涉及登录),使用transfer 当需要把aspx页面的输出结果插入到另一个aspx页面的时候使用 execute方法。 当然,忘记了还有一个超链接!当然这个就不需要讲太多了。他是在当需要用户来决定什么时候跳转页面,就使用超链接。 顺便提一下,如何使用redirect方法在查询字符串中使用汉字,因为经常的情况是出现乱码,原因是url不支持汉字。这个时候需要转换: 以下是引用片段: string message =server.urlencode("欢迎来到赛跑专栏"); 先转换,再使用查询字符串 以下是引用片段: response.redirect("webform2.aspx?msg="+message);

    时间:2018-04-25 关键词: 单片机

  • uC/OS-II 常用函数参考手册

     任务管理 1 OSTaskCreate() 建立一个新任务。任务的建立可以在多任务环境启动之前,也可以在正在运行的任务中建立。中断处理程序中不能建立任务。一个任务可以为无限循环的结构。 函数原型:INT8U OSTaskCreate(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio); 参数说明:task 是指向任务代码首地址的指针。 pdata 指向一个数据结构,该结构用来在建立任务时向任务传递参数。 ptos is a pointer to the task's top of stack. If the configuration constant OS_STK_GROWTH is set to 1, the stack is assumed to grow downward (i.e. from high memory to low memory). 'pstk' will thus point to the highest (valid) memory location of the stack. If OS_STK_GROWTH is set to 0, 'pstk' will point to the lowest memory location of the stack and the stack will grow with increasing memory locations. prio is the task's priority. A unique priority MUST be assigned to each task and the lower the number, the higher the priority. 返回值: OSTaskCreate() 的返回值为下述之一: * OS_NO_ERR:函数调用成功。 * OS_PRIO_EXIST:具有该优先级的任务已经存在。 * OS_PRIO_INVALID:参数指定的优先级大于OS_LOWEST_PRIO。 * OS_NO_MORE_TCB:系统中没有OS_TCB可以分配给任务了。 2 OSTaskSuspend() 无条件挂起一个任务。调用此函数的任务也可以传递参数 OS_PRIO_SELF,挂起调用任务本身。当前任务挂起后,只有其他任务才能唤醒被挂起的任务。任务挂起后,系统会重新进行任务调度,运行下一个优先级最高的就绪任务。唤醒挂起任务需要调用函数OSTaskResume()。 任务的挂起是可以叠加到其他操作上的。例如,任务被挂起时正在进行延时操作,那么任务的唤醒就需要两个条件:延时的结束以及其他任务的唤醒操作。又如,任务被挂起时正在等待信号量,当任务从信号量的等待对列中清除后也不能立即运行,而必须等到被唤醒后。 函数原型:INT8U OSTaskSuspend(INT8U prio); 参数说明:prio为指定要获取挂起的任务优先级,也可以指定参数 OS_PRIO_SELF,挂起任务本身。此时,下一个优先级最高的就绪任务将运行。 返回值: OSTaskSuspend() 的返回值为下述之一: * OS_NO_ERR:函数调用成功。 * OS_TASK_SUSPEND_IDLE:试图挂起μC/OS-II中的空闲任务(Idle task)。此为非法操作。 * OS_PRIO_INVALID:参数指定的优先级大于 OS_LOWEST_PRIO 或没有设定 OS_PRIO_SELF 的值。 * OS_TASK_SUSPEND_PRIO:要挂起的任务不存在。 3 OSTaskResume() 唤醒一个用 OSTaskSuspend() 函数挂起的任务。OSTaskResume() 也是唯一能“解挂”挂起任务的函数。 函数原型:INT8U OSTaskResume(INT8U prio); 参数说明:prio指定要唤醒任务的优先级。 返回值: OSTaskResume() 的返回值为下述之一: * OS_NO_ERR:函数调用成功。 * OS_TASK_RESUME_PRIO:要唤醒的任务不存在。 * OS_TASK_NOT_SUSPENDED:要唤醒的任务不在挂起状态。 * OS_PRIO_INVALID:参数指定的优先级大于或等于OS_LOWEST_PRIO。 信号量 1 OSSemCreate() 该函数建立并初始化一个信号量,信号量的作用如下: * 允许一个任务和其他任务或者中断同步 * 取得设备的使用权 * 标志事件的发生 函数原型:OS_EVENT *OSSemCreate(INT16U value); 参数说明:value 参数是所建立的信号量的初始值,可以取0到65535之间的任何值。 返回值: OSSemCreate() 函数返回指向分配给所建立的信号量的控制块的指针。如果没有可用的控制块,OSSemCreate() 函数返回空指针。 2 OSSemPend() 该函数用于任务试图取得设备的使用权、任务需要和其他任务或中断同步、任务需要等待特定事件的发生的场合。如果任务调用OSSemPend() 函数时,信号量的值大于零,OSSemPend() 函数递减该值并返回该值。如果调用时信号量值等于零,OSSemPend() 函数将任务加入该信号量的等待队列。OSSemPend() 函数挂起当前任务直到其他的任务或中断设置信号量或超出等待的预期时间。如果在预期的时钟节拍内信号量被设置,μC/OS-Ⅱ默认让最高优先级的任务取得信号量并回到就绪状态。一个被OSTaskSuspend() 函数挂起的任务也可以接受信号量,但这个任务将一直保持挂起状态直到通过调用OSTaskResume() 函数恢复该任务的运行。 函数原型:void OSSemPend ( OS_EVNNT *pevent, INT16U timeout, int8u *err ); 参数说明:pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。(参考OSSemCreate() 函数)。 timeout 允许一个任务在经过了指定数目的时钟节拍后还没有得到需要的信号量时恢复就绪状态。如果该值为零表示任务将持续地等待信号量,最大的等待时间为65535个时钟节拍。这个时间长度并不是非常严格的,可能存在一个时钟节拍的误差。 err 是指向包含错误码的变量的指针,返回的错误码可能为下述几种: * OS_NO_ERR :信号量不为零。 * OS_TIMEOUT :信号量没有在指定数目的时钟周期内被设置。 * OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断调用该函数,但μC/OS-Ⅱ仍然包含了检测这种情况的功能。 * OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针。 返回值:无 3 OSSemPost() 该函数用于设置指定的信号量。如果指定的信号量是零或大于零,OSSemPost() 函数递增该信号量的值并返回。如果有任何任务在等待该信号量,则最高优先级的任务将得到信号量并进入就绪状态。任务调度函数将进行任务调度,决定当前运行的任务是否仍然为最高优先级的就绪任务。 函数原型:INT8U OSSemPost(OS_EVENT *pevent); 参数说明:pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。(参考OSSemCreate() 函数)。 返回值: OSSemPost() 函数的返回值为下述之一: * OS_NO_ERR :信号量被成功地设置 * OS_SEM_OVF :信号量的值溢出 * OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针 时间 1 OSTimeDly() 该函数用于将一个任务延时若干个时钟节拍。如果延时时间大于0,系统将立即进行任务调度。延时时间的长度可从0到65535个时钟节拍。延时时间0表示不进行延时,函数将立即返回调用者。延时的具体时间依赖于系统每秒钟有多少个时钟节拍(由文件OS_CFG.H中的OS_TICKS_PER_SEC宏来设定)。 函数原型:void OSTimeDly (INT16U ticks); 参数说明:ticks 为要延时的时钟节拍数。 返回值:无 2 OSTimeDlyHMSM() 该函数用于将一个任务延时若干时间。延时的单位是小时、分、秒、毫秒。调用 OSTimeDlyHMSM() 后,如果延时时间不为0,系统将立即进行任务调度。 函数原型: INT8U OSTimeDlyHMSM (INT8U hours,INT8U minutes,INT8U seconds,INT16U milli); 参数说明:hours 为延时小时数,范围从0-255。 minutes 为延时分钟数,范围从0-59。 seconds 为延时秒数,范围从0-59 milli 为延时毫秒数,范围从0-999。 需要说明的是,操作系统在处理延时操作时都是以时钟节拍为单位的,实际的延时时间是时钟节拍的整数倍。如果系统时钟节拍的间隔是10ms,而设定延时为5ms的话,则不会产生延时操作;而如果设定延时为15ms,则实际的延时是两个时钟节拍,也就是20ms。 返回值: OSTimeDlyHMSM() 的返回值为下述之一: * OS_NO_ERR:函数调用成功。 * OS_TIME_INVALID_MINUTES:参数错误,分钟数大于59。 * OS_TIME_INVALID_SECONDS:参数错误,秒数大于59。 * OS_TIME_INVALID_MILLI:参数错误,毫秒数大于999。 * OS_TIME_ZERO_DLY:四个参数全为0。 内存管理 1 OSMemCreate() 该函数建立并初始化一个用于动态内存分配的区域,该内存区域包含指定数目的、大小确定的内存块。应用可以动态申请这些内存块并在用完后将其释放回这个内存区域。该函数的返回值就是指向这个内存区域控制块的指针,并作为OSMemGet(),OSMemPut(),OSMemQuery() 等相关调用的参数。 函数原型:OS_MEM *OSMemCreate( void *addr, INT32U nblks, INT32U blksize, INT8U *err ); 参数说明:addr 建立的内存区域的起始地址。可以使用静态数组或在系统初始化时使用 malloc() 函数来分配这个区域的空间。 nblks 内存块的数目。每一个内存区域最少需要定义两个内存块。 blksize 每个内存块的大小,最小应该能够容纳一个指针变量。 err 是指向包含错误码的变量的指针。Err可能是如下几种情况: * OS_NO_ERR :成功建立内存区域。 * OS_MEM_INVALID_ADDR :非法地址,即地址为空指针。 * OS_MEM_INVALID_PART :没有空闲的内存区域。 * OS_MEM_INVALID_BLKS :没有为内存区域建立至少两个内存块。 * OS_MEM_INVALID_SIZE :内存块大小不足以容纳一个指针变量。 返回值: OSMemCreate() 函数返回指向所创建的内存区域控制块的指针。如果创建失败,函数返回空指针。 2 OSMemGet() 该函数用于从内存区域分配一个内存块。用户程序必须知道所建立的内存块的大小,并必须在使用完内存块后释放它。可以多次调用 OSMemGet() 函数。它的返回值就是指向所分配内存块的指针,并作为 OSMemPut() 函数的参数。 函数原型:void *OSMemGet(OS_MEM *pmem, INT8U *err); 参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate() 函数的返回值中得到。 err 是指向包含错误码的变量的指针。Err可能是如下情况: * OS_NO_ERR :成功得到一个内存块。 * OS_MEM_NO_FREE_BLKS :内存区域中已经没有足够的内存块。 返回值: OSMemGet() 函数返回指向所分配内存块的指针。如果没有可分配的内存块,OSMemGet() 函数返回空指针。 3 OSMemPut() 该函数用于释放一个内存块,内存块必须释放回它原先所在的内存区域,否则会造成系统错误。 函数原型:INT8U OSMemPut (OS_MEM *pmem, void *pblk); 参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate() 函数的返回值中得到。 pblk 是指向将被释放的内存块的指针。 返回值: OSMemPut() 函数的返回值为下述之一: * OS_NO_ERR :成功释放内存块 * OS_MEM_FULL :内存区域已满,不能再接受更多释放的内存块。这种情况说明用户程序出现了错误,释放了多于用 OSMemGet() 函数得到的内存块。 4 OSMemQuery() 该函数用于得到内存区域的信息。 函数原型:INT8U OSMemQuery(OS_MEM *pmem, OS_MEM_DATA *pdata); 参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate() 函数的返回值中得到。 pdata 是一个指向 OS_MEM_DATA 数据结构的指针,该数据结构包含了以下的域: void OSAddr; /* 指向内存区域起始地址的指针 */ void OSFreeList; /* 指向空闲内存块列表起始地址的指针 */ INT32U OSBlkSize; /* 每个内存块的大小 */ INT32U OSNBlks; /* 该内存区域中的内存块总数 */ INT32U OSNFree; /* 空闲的内存块数目 */ INT32U OSNUsed; /* 已使用的内存块数目 */ 消息队列 1 OSQCreate() 该函数用于建立一个消息队列。任务或中断可以通过消息队列向一个或多个任务发送消息。消息的含义是和具体的应用密切相关的。 函数原型:OS_EVENT *OSQCreate (void **start, INT8U size); 参数说明:start 是消息内存区的首地址,消息内存区是一个指针数组。 size 是消息内存区的大小。 返回值: OSQCreate() 函数返回一个指向消息队列控制块的指针。如果没有空闲的控制块,OSQCreate() 函数返回空指针。 2 OSQPend() 该函数用于任务等待消息。消息通过中断或任务发送给需要的任务。消息是一个指针变量,在不同的应用中消息的具体含义不同。如果调用 OSQPend() 函数时队列中已经存在消息,那么该消息被返回给 OSQPend() 函数的调用者,该消息同时从队列中清除。如果调用 OSQPend() 函数时队列中没有消息,OSQPend() 函数挂起调用任务直到得到消息或超出定义的超时时间。如果同时有多个任务等待同一个消息,μC/OS-Ⅱ默认最高优先级的任务取得消息。一个由 OSTaskSuspend() 函数挂起的任务也可以接受消息,但这个任务将一直保持挂起状态直到通过调用 OSTaskResume() 函数恢复任务的运行。 函数原型:void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err); 参数说明:pevent 是指向消息队列的指针,该指针的值在建立该队列时可以得到。(参考 OSQCreate() 函数)。 timeout 允许一个任务以指定数目的时钟节拍等待消息。超时后如果还没有得到消息则恢复成就绪状态。如果该值设置成零则表示任务将持续地等待消息,最大的等待时间为65535个时钟节拍。这个时间长度并不是非常严格的,可能存在一个时钟节拍的误差。 err 是指向包含错误码的变量的指针。OSQPend() 函数返回的错误码可能为下述几种: * OS_NO_ERR :消息被正确地接受。 * OS_TIMEOUT :消息没有在指定的时钟周期数内接收到消息。 * OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断中调用该函数,但μC/OS-Ⅱ仍然包含了检测这种情况的功能。 * OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 返回值: OSQPend() 函数返回取得的消息并将 *err 置为 OS_NO_ERR。如果没有在指定数目的时钟节拍内接受到消息,OSQPend() 函数返回空指针并将 *err 设置为 OS_TIMEOUT。 3 OSQPostFront() 该函数用于向消息队列发送消息。OSQPostFront() 函数和 OSQPost() 函数非常相似,不同之处在于 OSQPostFront() 函数将发送的消息插到消息队列的最前端。也就是说,OSQPostFront() 函数使得消息队列按照后入先出(LIFO)的方式工作,而不是先入先出(FIFO)。消息是一个指针长度的变量,在不同的应用中消息的含义也可能不同。如果队列中已经存满消息,则此调用将返回错误码。OSQPost() 函数也是如此。在调用此函数时如果有任何任务在等待队列中的消息,则最高优先级的任务将得到这个消息。如果等待消息的任务优先级比发送消息的任务优先级高,那么高优先级的任务在得到消息后将立即抢占当前任务执行,也就是说,将发生一次任务切换。 函数原型:INT8U OSQPostFront(OS_EVENT *pevent, void *msg); 参数说明:pevent 是指向即将接收消息的消息队列的指针。该指针的值在建立队列时可以得到。(参考 OSQCreate() 函数)。 msg 是即将发送的消息的指针。不允许传递一个空指针。 返回值: OSQPostFront() 函数的返回值为下述之一: * OS_NO_ERR :消息成功地放到消息队列中。 * OS_Q_FULL :消息队列已满。 * OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 4 OSQPost() 该函数用于向消息队列发送消息。消息是一个指针长度的变量,在不同的应用中消息的含义也可能不同。如果队列中已经存满消息,则此调用返回错误码。如果有任何任务在等待队列中的消息,则最高优先级的任务将得到这个消息。如果等待消息的任务优先级比发送消息的任务优先级高,那么高优先级的任务将在得到消息后立即抢占当前任务执行,也就是说,将发生一次任务切换。消息是以先入先出(FIFO)方式进入队列的,即先进入队列的消息先被传递给任务。 函数原型:INT8U OSQPost(OS_EVENT *pevent, void *msg); 参数说明:pevent 是指向即将接受消息的消息队列的指针。该指针的值在建立队列时可以得到。(参考 OSQCreate() 函数)。 msg 是即将发送给队列的消息。不允许传递一个空指针。 返回值: OSQPost() 函数的返回值为下述之一: * OS_NO_ERR :消息成功地放到消息队列中。 * OS_Q_FULL :消息队列已满。 * OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 5 OSQFlush() 该函数用于清空消息队列。 函数原型:INT8U *OSQFlush(OS_EVENT *pevent); 参数说明:pevent 是指向消息队列的指针。该指针的值在建立队列时可以得到。(参考 OSQCreate() 函数)。 返回值: OSQFlush() 函数的返回值为下述之一: * OS_NO_ERR :消息队列被成功清空 * OS_ERR_EVENT_TYPE :试图清除不是消息队列的对象 6 OSQQuery() 该函数用来取得消息队列的信息。用户程序必须建立一个 OS_Q_DATA 的数据结构,该结构用来保存从消息队列的控制块得到的数据。通过调用该函数可以知道是否有任务在等待消息、有多少个任务在等待消息、队列中有多少消息以及消息队列可以容纳的消息数。OSQQuery() 函数还可以得到即将被传递给任务的消息。 函数原型:INT8U OSQQuery(OS_EVENT *pevent, OS_Q_DATA *pdata); 参数说明:pevent 是指向消息队列的指针。该指针的值在建立消息队列时可以得到。(参考 OSQCreate() 函数)。 pdata 是指向 OS_Q_DATA 数据结构的指针,该数据结构包含下述成员: void *OSMsg; /* 下一个可用的消息 */ INT16U OSNMsgs; /* 队列中的消息数目 */ INT16U OSQSize; /* 消息队列的大小 */ INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* 消息队列的等待队列 */ INT8U OSEventGrp; 返回值: OSQQuery() 函数的返回值为下述之一: * OS_NO_ERR :调用成功 * OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 7 OSQDel() 该函数用于删除指定的消息队列。

    时间:2018-04-25 关键词: uc/os-ii

  • 单片机执行程序的过程,加深对51单片机指令的理解

     为了加深初学者对51单片机指令的理解,现在把指令执行的过程在此详细说明,希望对你有启发! 单片机执行程序的过程,实际上就是执行我们所编制程序的过程。即逐条指令的过程。计算机每执行一条指令都可分为三个阶段进行。即取指令-----分析指令-----执行指令。 取指令的任务是:根据程序计数器PC中的值从程序存储器读出现行指令,送到指令寄存器。 分析指令阶段的任务是:将指令寄存器中的指令操作码取出后进行译码,分析其指令性质。如指令要求操作数,则寻找操作数地址。 计算机执行程序的过程实际上就是逐条指令地重复上述操作过程,直至遇到停机指令可循环等待指令。 一般计算机进行工作时,首先要通过外部设备把程序和数据通过输入接口电路和数据总线送入到存储器,然后逐条取出执行。但单片机中的程序一般事先我们都已通过写入器固化在片内或片外程序存储器中。因而一开机即可执行指令。 下面我们将举个实例来说明指令的执行过程: 开机时,程序计算器PC变为0000H。然后单片机在时序电路作用下自动进入执行程序过程。执行过程实际上就是取出指令(取出存储器中事先存放的指令阶段)和执行指令(分析和执行指令)的循环过程。 例如执行指令:MOV A,#0E0H,其机器码为“74H E0H”,该指令的功能是把操作数E0H送入累加器, 0000H单元中已存放74H,0001H单元中已存放E0H。当单片机开始运行时,首先是进入取指阶段,其次序是: 1 程序计数器的内容(这时是0000H)送到地址寄存器; 2 程序计数器的内容自动加1(变为0001H); 3 地址寄存器的内容(0000H)通过内部地址总线送到存储器,以存储器中地址译码电跟,使地址为0000H的单元被选中; 4 CPU使读控制线有效; 5 在读命令控制下被选中存储器单元的内容(此时应为74H)送到内部数据总线上,因为是取指阶段,所以该内容通过数据总线被送到指令寄存器。至此,取指阶段完成,进入译码分析和执行指令阶段。 由于本次进入指令寄存器中的内容是74H(操作码),以译码器译码后单片机就会知道该指令是要将一个数送到A累加器,而该数是在 这个代码的下一个存储单元。所以,执行该指令还必须把数据(E0H)从存储器中取出送到CPU,即还要在存储器中取第二个字节。其过程 与取指阶段很相似,只是此时PC已为0001H。指令译码器结合时序部件,产生74H操作码的微操作系列,使数字E0H从0001H单元取出。因为 指令是要求把取得的数送到A累加器,所以取出的数字经内部数据总线进入A累加器,而不是进入指令寄存器。至此,一条指令的执行完 毕。单片机中PC="0002H",PC在CPU每次向存储器取指或取数时自动加1,单片机又进入下一取指阶段。这一过程一直重复下去,直至收到 暂停指令或循环等待指令暂停。CPU就是这样一条一条地执行指令,完成所有规定的功能。

    时间:2018-04-25 关键词: 单片机指令 单片机

  • STM32学习经验总结

     1. 今天把普中开发板上的RS232芯片烧坏了,所以接下来要完成stlink的使用,来下载程序,同时也尝试调试程序,也算是被逼的去学一个很有用的工具,本来我都不想学stlink的。 2. 先学习一下stm32的定时器,过程中,加入stlink的下载和调试。 3. 定时器:stm32分高级定时器(TIM1和TIM8),通用定时器(TIM2-5),基本定时器(TIM6,7)。 4. 通用定时器(TIM2-5):1)位于低速APB1总线上。2)预分频:将计数器时钟频率降低。3)各个通用定时器之间是完全独立的。4)向上计数:从0到加载值,其他类似。5)定时器的时钟来源,可以来源于内部的APB1时钟倍频提供,这是时钟来源之一,但是用的比较多,也比较简单。 5. 通用定时器工作过程,只要有印象就可以,在配置的时候知道在配置什么。 6. 这是时钟的计算方法,其中APB1的时钟来源于AHB,如果APB1 = AHB,也就是APB1 = 1 * AHB,那么,此时的CK_INT = APB1,而如果APB1 = AHB / 2,那么这时候CK_INT = 2 * APB1的时钟,得到定时器的原始时钟。 7. 溢出时间 Tout = (ARR + 1)*(PSC+1) / TCLK,就是Tclk /(PSC+1)的倒数,得到原始时钟被分频后的频率,倒数就是时钟周期,那么再乘以重装值,就得到了溢出时间。一般时钟TCLK为72MHz。 8. 现在为战舰开发板,LED0->PB5红色 LED1->PE5蓝色 9. 注意:在要使用相应的库函数的时候,需要添加库函数的.c文件到FWLIB中,但是在使用template工程模板的时候不用考虑,已经全部加载进去了,我们只要写自己的功能函数就可以。 10. 定时器初始化步骤:1)使能定时器时钟APB1。2)初始化定时器,配置ARR,PSC,以及计数方式。3)使能定时器中断,配置NVIC,不要忘了在主函数中加入设置中断优先级分组的一句!4)使能定时器自身。5)编写中断服务函数。具体参数配置见功能函数中注释。 11. 注:电平的翻转,好像可以使用~ 或者 !实现。 ———————————— 1. 在学习定时器PWM波之前,先学一下stlink如何调试程序的。 2. stlink调试:1)首先关于引脚,PA13-15 以及 PB3 PB4都被默认为JTAG的引脚,所以工程中尽量不要使用这些引脚。2)在debug窗口查看变量的变化,可以选中程序中的变量,右击,然后add towatch1,就可以了。 3. 注:仿真分软件仿真和硬件仿真,那么软件仿真时,会有串口数据输出,但是硬件仿真的时候必须使用串口调试软件才能看到串口打印的数据,同理,逻辑分析也是这样的,可以使用逻辑分析仪采集板子引脚,做逻辑分析,那么keil上的debug的用处是观察变量,以及寄存器的变化,并且可以通过单步调试查看硬件执行情况。好像并不是很有用。但是keil的load的确方便,直接就可以下到板子里,不用我另外使用软件下载。 4. 定时器PWM:ARR值确定频率,CCRx确定占空比信号,可以理解为一个临界值。 6. 关于通道重映射问题:通道的重映射根据上图,可以看到,定时器三的通道二的部分重映射正好可以映射到PB5,led灯输出端,便于显示。注:一旦设置TIM3为部分重映射,那么四个通道都重映射了,也就是现在CH1->PB4了,没有专门单独设置CH2的映射。 7. 关于PWM的工作过程设置问题: 8. 关于PWM工作模式问题:CCMR1控制的是PWM模式1还是2。模式1的话,当CNT < CCR时,输出有效电平(有效电平到底是低电平还是高电平时根据CCER确定的),其余为无效电平;那么PWM模式2就和模式1相反。 9. 第7点,其中的CC1P为高极性,那么就是高电平有效。 10. //设置占空比,占空比=(CCRx/ARR)*100%或(TIM_Pulse/TIM_Period)*100% TimOCInitStructure.TIM_Pulse= 400-1; 这里我理解的是,这个值就是被放入捕获比较寄存器中,即CCRx中,用于输出固定的占空比的PWM波。 ———————————— 1. 关于6050陀螺仪模块问题 2. 今天开始着手平衡小车项目,蓝牙模块不用关心,只要知道可以连接,再判断串口发来的指令即可。 3. 好像比较简单的是使用DMP,通过结合DMP,可以将我们的原始数据转换成四元素输出,再通过四元素算出欧拉角,从而得到yawroll 和 pitch。 4. 因为做的是平衡小车,那么就不需要roll 和 yaw,直接通过pitch即可。 2016/4/1 1. 昨天卡在一个比较愚蠢的问题上,就是我很少自己配置工程什么的,所以当测试卖家发来的例程的时候,编译不通过,最后发现是要在keil4下打开才可以用,期中涉及的问题我也不想深究,毕竟配置工程什么的不是我学习的重点。 2. 今天总结了一条经验:解决问题,一定要善于使用最简单的方法!不做无用功,也不能浪费宝贵的时间!比如我发现他们的例程下进去,小车根本就不能动,排查问题,肯定是资料问题,我问了卖家以后,他们发了新的资料,成了!工程编译错误,网上百度一下,好像keil4下可以正常使用,那就装一个keil4,成了!这就是最简单的解决问题方法,不要自己死磕,不能转牛角尖。 3. MPU6050:首先我要做的就是能够使用例程,将MPU获取的数据打印出来,这样便于调试,也便于我接下来的学习;然后深入理解6050的工作方式和通信方式(IIC通信);再学习6050在DMP方法下,输出的数据怎么可以整定成小车的姿态,那么平衡小车中比较关键的内容就结束了! 因为还是写成项目日志比较清楚,所以准备写项目日志,暂停写直至平衡小车项目结束,重新开始学战舰开发板的其他模块。 ————————————  TFTLCD 1. 今天是平衡小车结束后,开始继续学习stm32的第一天,上次老师提出来要在mini2440上做摄像头循迹小车,又是小车!天呐~反正我已经快受不了了,而且arm的裸机开发和stm很相似,根本就没什么大的差别,而且arm是mini板,外设很少,学习模块的机会很少,所以我觉得,以后再stm32上做裸机开发,也可以尝试移植uC/OS系统,这样配套的学习,肯定比arm上裸机开发要好! 2. 我现在用的是战舰的3.5寸屏幕,IC为NT35310,电阻屏,16位并口驱动,分辨率为320*480,挺高的分辨率了已经算是。 3. 我现在想采用的是16位模式下,RGB565存储数据,这个型号的驱动芯片是NT35310(ID5310)。通过一些命令,我们可以设置GRAM(LCD的显存)的指针的增长方向,也就是一个个像素点的填写方向,所以只要设置一次开始写的位置,就可以把数据连续的填写,在SC++(X坐标起始位置)碰到EC(X坐标终止位置),这时候EC会返回SC,但同时SP++,从下一行开始填写像素点(在设置了指针增长方式为从左向右,从上往下模式时),而不需要关心位置,极大的方便了我们编程,速度也比每次都指定坐标的LCD屏要快。 4. 填写像素时,步骤为:设置起始位置---发送写GRAM命令---写入GRAM。 5. FSMC:灵活的静态存储控制器。这是STM32板子上带有的对SRAM、NAND Flash、NOR Flash等进行操作的控制器,那么现在对LCD的操作就相当于FSMC对一个SRAM在进行操作。 6. 直接切入LCD使用吧,反正原理大差不差的理解了,然后就观察他们是如何使用的就可以。 2016/4/20 IIC总线 由飞利浦公司提出 1. 因为IIC总线是一种非常常见而且普及的总线协议,用来对各种模块的寄存器读写从而进行配置,类似的还有SPI总线协议,虽然单片机里学过了,但是也差不多忘了,所以今天再温习一下。 2. 今天测试的也是例程上的,使用IIC对24C02进行读写操作,通过LCD屏幕显示。 3. IIC:有起始信号,停止信号,应答信号。 4. 硬件上已经将PB6接到了IIC_SCL上,将PB7接到了IIC_SDA上,这是因为这两个端口可以端口复用成IIC总线的两根线,模式时推挽输出。 5. GPIO:关于灵活控制某个端口为输入输出模式: SDA也就是PB7作为输出的时候,使用50MHz的输出速度,做普通输出(CNF1 CNF0 MODE1 MODE0 =0011);当SDA作为输入时,使用复用功能,MODE保留(CNF1 CNF0 MODE1 MODE0 = 1000) 6. IIC使用流程,我个人总结是这样的:1)配置IIC服用端口;2)检测IIC器件是否存在;3)由IIC发出起始信号,同时发送器件地址,接着就等待应答信号的产生;4)等待到了应答信号就可以发送数据出去,同样也需要进行应答信号的检测;5)那么读取其实也是类似的,具体的时序方面,其实就是根据手册上的时序图模拟一遍,注意位操作的正确性就可以了。 7. SPI协议:接着温习SPI协议,这个协议其实在我用单片机接NRF24L01射频通信模块的时候已经有了比较深刻的理解了,为什么说深刻,因为那时候还不想现在这么浮躁,会耐下性子,一步一步的从通信协议,配置寄存器开始做起,最后花了五六天才基本搞通互发,第一个煎熬的模块。 8. SPI是摩托罗拉公司提出的总线协议,这是一种同步,高速,全双工的串行通信协议。 9. 关于两个设置值,用来决定时钟的空闲信号和采样时机的CPOL和CPHA。 10. CPOL=0,串行时钟的空闲状态为低电平,反之为高电平;CPHA=0,则在第一个时钟跳变时做采样,CPHA=1,则在第二个时钟跳变时做采样。 11. 如何记忆MISO和MOSI,可以拆开理解为Master In Slave Out和 Master Out Slave In。 12. 根据我之前的理解,通俗的讲,SPI协议就是,小时候玩儿的你拍一我拍一,这种节奏就是时钟,这种击掌的过程就是数据线的传输,其实也还是不够贴切。应该是像两个小朋友交换同样个数的玩具,但是他们选择一换一的节奏逐个交换,直至他们的玩具全部交换完成,那么一个小朋友就有了原来属于另一个小朋友的玩具,那么这些玩具就是数据,用来装玩具的就是寄存器,用来以一定节奏交换就是时钟。 13. 现在例程上使用的是PB12做W25Q128的CS片选,PB13做SCK,PB14做MISO,PB15做MOSI。 —————————————— 485协议 1. 485协议:处于物理层,半双工,多点通信,它类似于RS232,但是它更加稳定可靠,传输距离比RS232要长,抗干扰性也不错,所以这个协议还是有必要做一些了解的。 2. 战舰上使用的是SP3485芯片,该芯片的原理图清楚的说明,当RE信号为低的时候,为接收模式,当RE信号为高时,设为了发送模式。 3. 好吧,其实和串口的模式非常相似,只是两者采用了不同的电压作为高低电平,其余实在是没多大差别。 ———————————————————— 1. CAN总线:也是一种串行通信协议,为现在欧洲汽车网络的标准协议,可以实现在汽车的LAN中的高速、高质量的数据传输,已经经过ISO国际标准化的串行通信协议。它通过两根线上的电位差来判断电平,总线电平分为显性电平和隐性电平,二者必有其一。 2. CAN总线的特点:可以根据优先级来决裁;不需要“地址”这个概念,加入模块方便;通信速度较快,也比较远,在现场控制中足够了;还有错误检测反馈等功能,使之成为公认的最有前途的现场总线之一 3. CAN总线电平判断:隐性电平是指CAN_L与CAN_H之间的电压差为0V,而显性电平为CAN_H与CAN_L之间的电压差为2.5V左右。 4. CAN总线通过五个帧实现:数据帧,遥控帧,错误帧,过载帧,帧间隔。 5. D:显性电平,也就是0,这时压差为2V左右;R:隐性电平,也就是1,这时的压差为0V。那么优先级仲裁就是通过显性电平的多少判断的,输出的显性电平多的优先级高;一般先发起总线请求的优先级要高于后来的,而且不能被打断。 6. CAN控制器:有三个邮箱,用于发送!发送过程:空闲邮箱(存在空闲邮箱时)----挂号(等待邮箱成为最高优先级)------预定(邮箱成为了最高优先级)------发送(一旦总线空闲了,就可以进行发送)----空闲(发送完成以后,再次把总线变为空闲)。 7. CAN控制器:有三级FIFO用来缓存数据!接收过程:FIFO中有一个为空时,接收到有效数据----挂号一-----又一次收到有效数据-----挂号二------。。。。超过三次收到有效数据但是没有被读走的话,就会产生溢出,所以需要及时的读取FIFO中的数据。 2016/4/24 摄像头OV7670 1. OV7670是CMOS VGA图像传感器,通过类IIC的SCCB总线控制,VGA可高达30帧/秒。 2. VGA,即分辨率为640*480的输出模式。QVGA,即分辨率为320*240的输出模式,一般用这种,因为OV7670的FIFO也只够存储一帧的QVGA。 2016/6/11 时隔近两个月,我又开始玩儿这个板子上的摄像头了,因为参加比赛的需要,必须要把摄像头原理和例程都消化,然后将摄像头改为可以采集灰度图像,并且可以灵活的设置图像阀值。 1. 使用类似IIC的SCCB(串行摄像头控制总线)控制总线,主要使用这个总线协议来对7670状态配置。 —————————————————————————— 上次又被考试打乱了,今天要搞一天。 1. 首先,OV7670正好可以存储一帧的分辨率大小为320*240,即QVGA格式。 2. PCLK为像素时钟,一般一个PCLK时钟输出一个像素,但要是一个像素为两个字节大小,那么就需要两个PCLK时钟才能输出一个完整的像素,例:RGB565。 3. VSYNC为帧同步信号,一帧出现一次。 4. HREF为行同步信号,这是根据分辨率中的多少行确定的,例如640*480的分辨率,那么一帧中就出现480次的这个行同步信号。 5. 图像数据就是通过D[7-0]八根数据线输出。 6. 根据时序图可以看出,当行同步信号为高电平有效的时候,数据的输出才为有效,那么再根据PCLK的上升沿MCU采集,下降沿数据改变,就可以实现图像数据的读取。(两个字节的话,高字节在前) 7. 7670工作流程:首先摄像头采集图像数据,然后存储到FIFO芯片AL224B(384K),一般我们只要存储一帧就够了,再通过接口,使用MCU从这个FIFO芯片中读取摄像头之前采集的数据。 8.这是针对FIFO操作的对外接口,也就是摄像头下面引出来的引脚。 9.从第一个帧同步信号开始,说明开始采集到了一帧,那么这时候让写指针复位,也就是从0地址开始写入,再把写使能使能,这时候FIFO中就会开始接收7670输入的图像数据,在等待到第二个帧同步信号以后,说明一帧图像采集完毕,这时候把写使能禁止,然后让MCU去读取FIFO中的数据,也不会让新的数据冲掉之前的数据,保证读取的正确。 11. 注:PB3 4 为JTAG中使用到的接口,如果要把这两个引脚作为普通的IO口,那么必须禁止JTAG,才能使得这两个引脚作为普通IO使用。 12. 采用中断获取帧同步信号,然后在OV7670往FIFO中写数据的同时,MCU也开始从FIFO中读取数据,这样可以加快数据的采集。 2016/6/16 1. "Y"表示明亮度(Luminance或Luma),也就是灰阶值;而"U"和"V" 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。 2. 也就是说,我主要用到的就是Y分量。 3. 几个重要的设置灰度图像输出的寄存器,需要进行了解。 寄存器地址 寄存器设置的值        7654 3210 解释 0x12 0x10 (0001 0000B) 通用控制寄存器,还是设置为QVGA格式输出,输出YUV编码方式的图像数据 0x3a 0x14 (0001 0100B) 行缓冲测试选项,使用固定的UV输出,这两个值就是0x67和0x68两个寄存器的值。行缓冲输出顺序,由这个寄存器的【3】(0)和0x3d的【0】(0)两位决定,对照发现为使用YUYV输出,也就是Y:U:V=4:2:2。 0x3d 0x80 (1000 0000B) 普通控制寄存器,伽马使能,同时和0x3a相互配合。 0x67 0x11 (0001 0001B) 填充U值的寄存器,值为0x11 0x68 0xff  (1111 1111B) 填充V值的寄存器,值为0xff 0x40 0xc0 (1100 0000B) 数据的输出范围为0x00-0xff。 4. 注:还是不是很理解输出顺序YUYV输出,也就是Y:U:V=4:2:2。这个意思。   1. 设置完成以后,基本就可以实现灰度图像的采集了,速度也挺快的。 2. 从网上了解了OV7670的特点以后,才明白之前的YUYV输出顺序的真正意义,其实不论Y还是U还是V都是八位二进制表示,也就是一个字节。那么YUYV输出格式表明,我们在使用的时候,只要读取第一个字节就可以获取Y(亮度),这个亮度可以进一步的处理判断,就可以通过阀值获得二值图像,也就是黑白图像。 3. 4. 这是我衣服上的标志,我还添加了手动设置阀值的按键操作,便于以后的调试,在不同的光线条件下可以进行调整。

    时间:2018-04-25 关键词: STM32

  • 裸奔已经可以满足需求了,那么为什么还要OS?

    为什么要裸奔? 理由1:裸奔已经可以满足需求了,那么为什么还要OS? 理由2:os很高大上,但是能被hold住么? 以上两点差不多就OK了。 一般说,单片机,就是搞点简单的东西。简单不是贬义,乔布斯总说自己要做最简单易用的作品,所以说我们遵从简单的设计原则,是没有问题的了。 那么如何裸奔呢? 看到21IC网站很多所谓大牛给出了所谓的架构,感觉高大山的架构,在我看来都是差的一塌糊涂,纯属照猫画虎,形式主义罢了。 最经典而完美的架构就是 main{ INT K ; init(); while(1) { INT J = K; } } IRQ { K++; } 就是前后台 的架构。当然这样说放到论坛里面肯定被喷。但这里没人喷。所以可以胡说八道了。 好的架构一定是隔离,大家互不干涉。说起来简单,做起来不易。如何做到互不干涉,有些是一定要有交叉了。比如定时器。首先提到定时器,因为太太重要了。基本山搞懂了定时器,就搞定了架构的很大一部分。看看哪些PLC的定时器的涉及,猜猜他们是怎么实现的。比如你可以实现这样一个定时器 if (timeup(n ms)) { //do something } 如果实现了这样一个函数,在各个所谓的任务中都用这个函数。基本上架构就很清晰了。 那么对于串口发送这种需要耗时的功能?咋办?如果有DMA,那么就用上。如果你有500个字节要发送,你还想实时性比较高,若没有DMA,那么很抱歉,裸奔没有办法搞定,即便是实时性OS,也很难办。如果接收方判断两个字符的间距的话,如modbus。那么发送必须是高优先级的,若采用os。 基本就这些了。 裸奔,感觉就是完全可以掌控得住。 如果要集成一些代码,比如我最近要集成canopen的代码,于是我就觉得我的代码整体可靠性要下降一档。心理上真的如此觉得。对自己就是这么自信。 如果要用os,那么也要熟读。弄透。然后采用。这样才觉得过瘾。否则感觉没有掌控力。 啰啰嗦嗦,说了废话,可见今天是废话连篇了。

    时间:2018-04-25 关键词: os 单片机

  • 根据自己理解,来仿写一个ucosii

     简单谈谈我对uc的一些认识级对于部分源码的分析和调试,作为对近一段时间学习的阶段性总结。下文将分两部分介绍,前半部分主要谈谈我个人对一些问题的认识以及一些疑惑,后半部分是通过阅读ucosii,按照ucosii的思路自己编写或者调试的一些源码的分析,这些源码可以实现任务按照优先级定时切换(MDK+stm32)。 1.ucosii有什么作用,和裸机的区别 uc是一个实时操作系统,很长一段时间以来我一直在纠结这个东西是干嘛用的,单片机不是有中断吗,为什么非要用这个东西来完成中断的功能呢? 先谈谈我们比较熟悉的裸机开发,使用一个while(1)配合一些中断来响应事件。但是我们知道,单片机的中断资源是有限的,并且多是用来响应外部事件。另外,中断中使用的全局变量,不可重入性也容易使系统产生问题,造成不确定性。而且中断时间不能过长,使得任务的吞吐量不能太大,而中断之间的相互嵌套也容易使程序出现问题。所以,在需要及时处理复杂或者耗时任务的时候(简单任务while循环的实时性好像不比uc差),及时响应任务并进行处理,这种普通的模式效果就比较差了。而uc有个好处就是它可以随时切换任务,每个任务的执行有固定的时间,通过操作系统统一的TimeTick可以有效统一任务运行的时间,这样就不会出现一个任务长期占据cpu而其他任务得不到运行的情况,我们可以通过调用uc的API来控制每个任务的运行。多任务还有个好处就是把复杂的程序拆成几个任务,这样管理相对方便,容易修改和扩展,否则复杂些几千行的while循环程序一旦完成,想再扩展就变得灰常麻烦。从另一方面讲,在做一些简单的东西的时候,不需要实时性的时候,感觉不使用uc反而更简单一些。想想既然NASA都在使用ucosii,这个东西肯定有它的价值的,学明白了肯定是有用的。 2.ucosii的数据结构简析 uc中的数据结构不是动态创建的,所以,在初始化操作系统的时候(OSInit (void)),要做的就是初始化这些数据结构。 其中又可以分为两个部分,第一是初始化全局变量,包括 OSTime = 0uL; 时间记录,TimeTick中自增 OSIntNesting = 0u; 中断嵌套层数,用于记录中断嵌套,中断中不允许任务调度 OSLockNesting = 0u; 调度锁 OSTaskCtr = 0u; 当前任务数 OSRunning = OS_FALSE; 操作系统禁止,在OSStart();中开启 OSCtxSwCtr = 0u; 任务切换数 OSIdleCtr = 0uL; 空闲任务数 #if OS_TASK_STAT_EN > 0u 后面几个没弄过,待查 OSIdleCtrRun = 0uL; OSIdleCtrMax = 0uL; OSStatRdy = OS_FALSE; #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 OSSafetyCriticalStartFlag = OS_FALSE; #endif 初始化完全局变量,就开始初始化各种链表,清空它们的内容然后穿起来。其中主要包括 任务控制块,消息块, 信号量集块,内存块几个主要块,uc的工作主要就靠它们了。 其中任务控制块是重中之重,用于记录任务的属性以及存储空间。在下文中具体分析其各变量的作用。 消息块主要用于任务同步,也就是任务间相互发送消息。 其中信号量用于协调共享资源的访问,说的简单点就是一个东西我用完了你再用。如果我正用着,这时候轮你用了,但是我没把权限给你(post),到你了你也不能用(pend)。这样就可以避免由不同任务同时访问一个资源而出错。 互斥信号量这个名字很奇怪,信号量的作用本来不就是互斥吗?互斥信号量是为了避免优先级反转用的。假设任务A,B,C优先级递增,A,C同时访问一个资源。如果使用信号量,当C访问资源时,如果C没来得及释放信号量,由于超时或者其他函数中有任务调度函数而发生任务调度时,A由于得不到C手中的信号量,任务不能执行,只能执行B任务了。这咋看没什么问题,但是仔细想想,当前任务优先级最高的A已经就绪但是不能运行,反而优先级低的B运行了,这优先级不是白设置了吗!究其原因,主要是C的问题,你作为最低的优先级,拿着信号量的时候发生任务调度,肯定不能再运行你自己了吧,信号量送不出去,最高的优先级的A只能干等,所以自然给了B。 哦,那你说上个锁算了,C运行的时候咱们谁也别打扰,目测这个办法也行,有待实验。 任务在获取互斥信号量后,可以把优先级提到最高,这样就可以避免被调度到B,直到 C释放了信号量。这是A优先级最高,信号量又没有被占用,OK,正常运行。 消息邮箱也是个信号量,不过它的作用是任务间通信,你可以通过它传递一个消息指针(*msg),这个指针你爱指什么指什么,反正你的任务指哪里,我的任务从哪里读取,你给我发邮件,我就去那里读,这样就可以互相通信了。不过必须等我post了,你pend上了才能成功读取,这可不是随便写个全局变量就可以随便读取的。可见,这也是一种消息间的协调机制。 消息队列我也不大清楚干什么用的,它能发好几个消息,每个任务只能读一个,下一个任务读下一个,至于做什么用的,有待深入学习。 信号量集也是一种任务间的通信方式,但是它的数据结构有别与消息类的。信号量集是在满足多个条件后触发一个任务的运行。如果说信号量是一把钥匙,信号量集就是多把钥匙,同时满足才能开门。另外,使用时要注意这个条件不是相加,而是按位相同。 最后时内存控制块,感觉像是uc的malloc和free,没用过。 3.任务调度时堆栈是个什么 我们知道,正常情况下我们写的函数中堆栈空间是由编译器自动分配的,那在任务里写函数为什么就要手动分配堆栈空间呢?这个堆栈中到底存储了什么东西呢。 通过阅读任务控制块代码我们可以知道,任务控制块中为我们提供一个函数指针,这个指针指向我们的任务,只要将这个指针的地址写入相应寄存器,那么程序就会自动运行到这个函数。但是我们注意,只有函数在被main直接或者间接调用时,计算机才会为函数分配响应内存空间,而我们现在只是用一个指针指向了这段即将运行的代码,至于空间,有可能是随意一段内存空间,体现在程序上就是函数中的局部变量和函数内部调用的函数所需要的数据,全都会保存在任意一段内存空间,覆盖有用数据,在M3上,这种随意侵蚀内存的行为将会引发错误中断,程序无法正常使用。同样的道理,这就好比我们定义数组时必须指定空间大小,如果随意定义一个指针然后对其所指空间赋值,极有可能引起严重错误。 至于任务空间大小,要看其全局变量和函数嵌套层数来定义,函数嵌套是需要很多内存空间的,嵌套层数越多,需要的空间越大。另外好像空间大小都是64,128,256这样的整形,我们打开debug窗口硬件仿真时也会发现,这些堆栈大小好像正好是64等整数,原因待查。 4.关于为什么不使用调度锁而使用互斥信号量的又一种解释 互斥信号量我们可以暂时提高当前任务的优先级,也就是说,我们可以把当前任务优先级提高到最高。同样,我们也可以不提高到最高,如果有优先级高于上文中提到的A任务的优先级的任务Z,如果我们使用调度锁,Z任务也是不能被调度的。而使用互斥信号量, 我们可以把互斥任务优先级提高到高于A但是低于Z的级别,这样既不会出现优先级反转的现象,同时还能及时响应任务Z。 程序源码分析: 以下源码是借鉴uc,使用比较简单的方式实现的一个任务调度模块,在MDK下调试,芯片为STM32F103ZET6。可以实现任务定时切换,简要描述了ucosii的任务调度基本功能。底层代码使用uc官方移植文件。

    时间:2018-04-25 关键词: ucosii

  • 单片机学习步骤工程师的经验

     有了单片机学习板之后你就要多练习,把学习板和电脑连好,打开调试软件坐在电脑前,先学会怎么用调试软件,然后从最简单的流水灯实验做起,等你能让那八个流水灯按照你的意愿随意流动时你已经入门了,你会发现单片机是多么迷人的东西啊,太好玩了,这不是在学习知识,而是在玩,当你编写的程序按你的意愿实现时你比做什么事都开心,你会上瘾的,真的。做电子类的人真的会上瘾。然后让数码管亮起来,这两项会了后,你已经不能自拔了,你已经开始考虑你这辈子要走哪一行了。就是要这样练习,在写程序的时候你肯定会遇到很多问题,而这时你再去翻书找,或是问别人,当得到解答后你会记住一辈子的,知识必须用于现实生活中,解决实际问题,这样才能发挥它的作用。另外我再说说用汇编和C语言编程的问题。很多同学大一二就开设了C语言的课 ,我也上过,我知道那时天天就是几乘几,几加几啊,求个阶乘啊。学完了有什么用?让你用C语言编单片机的程序你是不是就傻了?书上的东西我们必须要会运用。单片机编程用C语言或汇编语言都可以,但是我建议用C语言比较好,如果原来有C语言的基础那学起来会更好,如果没有,也可以边学单片机边学C语言,C 语言也挺简单,只是一门工具而已,我劝你最好学会,将来肯定用得着,要不你以后也得学,你一点汇编都不会根本无所谓,但你一点C语言都不会那你将来会吃苦头。汇编写程序代码效率高,但相对难度较大,而且很罗嗦,尤其是遇到算法方面的问题时,根本是麻烦的不得了,现在单片机的主频在不断的提高,我们完全不需要那么高效率的代码,因为有高频率的时钟,单片机的ROM也在不断的提高,足够装得下你用C语言写的任何代码,C语言的资料又多又好找,将来可移植性非常好,只需要变一个IO口写个温度传感器的程序在哪里都能用,所以我劝大家用C语言。总结上面,只要你有信心,做事能坚持到底,有不成功不放弃的强烈意志,那学个单片机来说就是件非常容 易的事。 步骤: 1.找本书大概了解一下单片机结构,大概了解就行。不用都看懂的。 2.找学习板练习编写程序,学单片机就是练编程序,遇到不会的再问人或查书。 3.自己网上找些小电路类的资料练习设计外围电路。焊好后自己调试,熟悉过程。 4.自己完全设计具有个人风格的电路,产品,。。。你已经是高手了 ^_^。 。。。。 学习单片机的八大步骤 学习使用单片机就是理解单片机硬件结构,以及内部资源的应用,在汇编或C语言中学会各种功能的初始化设置,以及实现各种功能的程序编制。 第一步:数字I/O的使用 使用按钮输入信号,发光二极管显示输出电平,就可以学习引脚的数字I/O功能,在按下某个按钮后,某发光二极管发亮,这就是数字电路中组合逻辑的功能,虽然很简单,但是可以学习一般的单片机编程思想,例如,必须设置很多寄存器对引脚进行初始化处理,才能使引脚具备有数字输入和输出输出功能。每使用单片机的一个功能,就要对控制该功能的寄存器进行设置,这就是单片机编程的特点,千万不要怕麻烦,所有的单片机都是这样。 第二步:定时器的使用 学会定时器的使用,就可以用单片机实现时序电路,时序电路的功能是强大的,在工业、家用电气设备的控制中有很多应用,例如,可以用单片机实现一个具有一个按钮的楼道 灯开关,该开关在按钮按下一次后,灯亮3分钟后自动灭,当按钮连续按下两次后,灯常亮不灭,当按钮按下时间超过2s,则灯灭。数字集成电路可以实现时序电路,可编程逻辑器件(PLD)可以实现时序电路,可编程控制器(PLC)也可以实现时序电路,但是只有单片机实现起来最简单,成本最低。定时器的使用是非常重要的,逻辑加时间控制是单片机使用的基础。 第三步:中断 单片机的特点是一段程序反复执行,程序中的每个指令的执行都需要一定的执行时间,如果程序没有执行到某指令,则该指令的动作就不会发生,这样就会耽误很多快速发生的事情,例如,按钮按下时的下降沿。要使单片机在程序正常运行过程中,对快速动作做出反应,就必须使用单片机的中断功能,该功能就是在快速动作发生后,单片机中断正常运行的程序,处理快速发生的动作,处理完成后,在返回执行正常的程序。中断功能使用中的困难是需要精确地知道什么时候不允许中断发生(屏蔽中断)、什么时候允许中断发生(开中断),需要设置哪些寄存器才能使某种中断起作用,中断开始时,程序应该干什么,中断完成后,程序应该干什么等等 。 中断学会后,就可以编制更复杂结构的程序,这样的程序可以干着一件事,监视着一件事,一旦监视的事情发生,就中断正在干的事情,处理监视的事情,当然也可以监视多个事情,形象的比喻,中断功能使单片机具有吃着碗里的,看着锅里的功能。 以上三步学会,就相当于降龙十八掌武功,会了三掌了,可以勉强护身。 第四步:与PC机进行RS232通信 单片机都有USART接口,特别是MSP430系列中很多型号,都具有两个USART接口。USART接口不能直接与PC机的RS232接口连接,它们之间的逻辑电平不同,需要使用一个MAX3232芯片进行电平转换。 USART接口的使用是非常重要的,通过该接口,可以使单片机与PC机之间交换信息,虽然RS232通信并不先进,但是对于接口的学习是非常重要的。正确使用USART接口,需要学习通信协议,PC机的RS232接口编程等等知识。试想,单片机实验板上的数据显示在PC机监视器上,而PC机的键盘信号可以在单片机实验板上得到显示,将是多么有意思的事情啊! 第五步:学会A/D转换 MAP430单片机带有多通道12位A/D转换器,通过这些A/D转换器可以使单片机操作模拟量,显示和检测电压、电流等信号。学习时注意模拟地与数字地、参考电压、采样时间,转换速率,转换误差等概念。使用A/D转换功能的简单的例子是设计一个电压表。 第六步:学会PCI、I2C接口和液晶显示器接口 这些接口的使用可以使单片机更容易连接外部设备,在扩展单片机功能方面非常重要。 第七步:学会比较、捕捉、PWM功能 这些功能可以使单片机能够控制电机,检测转速信号,实现电机调速器等控制起功能。 如果以上七步都学会,就可以设计一般的应用系统,相当于学会十招降龙十八掌,可以出手攻击了。 第八步:学习USB接口、TCP/IP接口、各种工业总线的硬件与软件设计 学习USB接口、TCP/IP接口、各种工业总线的硬件与软件设计是非常重要的,因为这是当前产品开发的发展方向。

    时间:2018-04-25 关键词: 单片机

  • 我的单片机学习过程和经验分享

     学习使用单片机只能靠循序渐进的积累,下面就本人学习单片机的过程和经验做简要介绍。 首先,学习单片机要有一定的基础:电子技术方面要有数字电路和模拟电路等方面的理论基础,特别是数字电路;编程语言要求汇编语言或C语言。要想成为单片机高手,建议初学者首先学习汇编语言,学的差不多的时候,转入C语言学习。尽管汇编语言属于低级语言,编程效率低,但是较C语言具有目标代码简短,占用内存少,执行速度快等优点,更重要的是能使初学者尽快熟悉单片机的内部结构,并能对其进行精确的控制。汇编语言在单片机教材里面都会涉及,不需要单独购买教材和学习。C语言是一门学问,有很多专业书籍来讲解,并且对我们今后的编程生涯有绝对的好处,因此要深入学习,千万不要自以为看了某某的视频教程就以为掌握了C语言,那只是C语言的一部分。在这里给大家推荐一本单片机C语言程序设计参考书,马忠梅等著,北京航空航天大学出版社出版的《单片机的C语言应用程序设计》,要求C语言基础。如果没学过C语言,建议学习清华大学谭浩强编写的C语言程序设计,这本书写的不错,通俗易懂。 其次,是单片机教材选择。单片机是一门非常重视实践的技术,不能总是看书,但要学习它首先应看书,对单片机引脚、内部结构、寄存器和原理有一定地了解和感官认识,它的是怎样工作的,能干些什么?刚开始时,也许你看不明白,但这并不要紧,因为你还缺乏实践经验。现在单片机应用广泛,因此各个厂家分别推出了自己的单片机,按内部结构体系派系分:51系列、PIC系列、AVR系列、摩托罗拉等等……我们没必要每样都学!因为他们的编程方法和调试过程以及内部指令结构有一定的相似,只要学精通一款就OK了!尤其是用C语言编程,就几乎不用分什么派系,但是我们要选择一款有代表性的知识范围广,并且入门容易,书籍多。一般来说,MCS-51系列单片机已经得到广泛的普及和应用,市场上它的资料也比较多,用的人也很多。给大家推荐一些参考书,学习时只需要一本就足够拉。相关教材还有很多,在这不一一列举。 然后,是开发工具和开发环境的选择。选择一块合适的学习板,对于初学者来说一般无力接受,如果经济条件允许、本人又对单片机很感兴趣、有从事相关工作意向的话,鼓励大家购买。随便说一句,学习板功能要求太全,具有流水灯、数码管、独立键盘、矩阵键盘、AD或DA、液晶、蜂鸣器等就差不多啦,毕竟,功能齐全的价格比较高。仿真器对单片机初学者来说既是那么耳熟,同时又有些陌生,这主要是因为市场上传统的仿真器价格都在千元以上,对经济不是非常宽裕的人来说是不小的开支。同时仿真器是用来提高调试程序效率的,也不是非需不可的,如果你没有仿真器,遇到程序出错的时候,只好苦思冥想,反复烧写调试。随便推荐一下,学林电子的51tracer仿真器,有兴趣的朋友可关注一下。有了单片机教程板以后,先看下指导说明书,熟悉一下学习板,开卷有益。以后就得靠自己多练习了,将学习板与电脑连接好,先学会开发软件的使用,然后从最简单的流水灯实验做起,按照你自己的意愿控制流水灯,当你完成时,你会发现这是多么惬意的事情。太好玩了,你会觉得这不是在学习,而是在玩,当你发现,单片机能够按照你编写的程序工作时,你会觉得非常兴奋,比做什么事情都开心,这样你会慢慢迷上单片机,真的。不少网站上说搞定某个实验,就恭维的告诉你一声”恭喜你,学会了”自己学会了单片机,这有点可笑,这只能说明你算过关了,对单片机有了一定了解和会使用它了。但是单片机能完成的功能太多了,尤其是对外围器件的控制,综合起来能设计出许多意想不到的产品.因此除了入门外,精通可千万别轻易说出口。

    时间:2018-04-25 关键词: 单片机学习 单片机

  • 74LS 系列与 74HC,74HCT,CD系列的区别

     74LS 系列与 74HC,74HCT,CD系列的区别: 1.LS、HC 二者高电平低电平定义不同:HC 高电平规定为 0.7 倍电源电压,低电平规定为 0.3 倍电源电压。LS 规定高电平为 2.0V,低电平为 0.8V。 带负载特性不同。 2.HC 上拉下拉能力相同,LS 上拉弱而下拉强。 3.输入特性不同:HC 输入电阻很高,输入开路时电平不定。LS 输入内部有上拉,输入开路时为高电平。 4.74LS 系列是“低功耗肖特基 TTL”,统称 74LS 系列。其改进型为“先进低功耗肖特基 TTL”,既 74ALS 系列,它 的性能比 74LS 更好。 5.74HC 系列,它具有 CMOS 的低功耗和相当于 74LS 高速度的性能,属于一种高速低功耗产品。 6.74HC 系列与 74LS 的工作频率都在 30mHz 以下,74ALS 略高,可达 50mHz。 7.工作电压却大不相同:74LS 系列为 5V,74HC 系列为 2~6V。 8.扇出能力:74LS 系列为 20,而 74HC 系列在直流时则高达 1000 以上,但在交流时很低,由工作频率决定。 9.74hc 与 74hct 都是高速 CMOS 器件,是同一系列,其中 74hct 的输入信号为 TTL电平. 10.74hc与74hct都是高速CMOS器件,是同一系列,其中74hct的输入信号为TTL电平. .

    时间:2018-04-25 关键词: 74hc535

  • 串口波特率选择

     波特率(Boud Rate)就是在串口通信中每秒能够发送的位数(bits/second)。MCS-51串行端口在四种工作模式下有不同的波特率计算方法。其中,模式0和模式2波特率计算很简单;模式1和模式3的波特率选择相同,故在此仅以工作模式1为例来说明串口通信波特率的选择。 在串行端口工作于模式1,其波特率将由计时/计数器1来产生,通常设置定时器工作于模式2(自动再加模式)。在此模式下波特率计算公式为: 波特率=(1+SMOD)*晶振频率/(384*(256-TH1)) 其中,SMOD——寄存器PCON的第7位,称为波特率倍增位; TH1——定时器的重载值。 在选择波特率的时候需要考虑两点:首先,系统需要的通信速率。这要根据系统的运作特点,确定通信的频率范围。然后考虑通信时钟误差。使用同一晶振频率在选择不同的通信速率时通信时钟误差会有很大差别。为了通信的稳定,我们应该尽量选择时钟误差最小的频率进行通信。 下面举例说明波特率选择过程: 假设系统要求的通信频率在20000bit/s以下,晶振频率为12MHz,设置SMOD=1(即波特率倍增)。则 TH1=256-62500/波特率 根据波特率取值表,我们知道可以选取的波特率有:1200,2400,4800,9600,19200。列计数器重载值,通信误差如下表: 波特率 计数器重载值 波特率误差 1200 204 0.16% 2400 230 0.16% 4800 243 0.16% 9600 249 6.99% 19200 253 8.51% 因此,在通信中,最好选用波特率为1200,2400,4800中的一个。

    时间:2018-04-23 关键词: 波特率 串口

  • 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-04-23 关键词: STM32 51单片机 单片机

  • 51单片机定时器使用经验总结

     单片机定时器的使用可以说非常简单,只要掌握原理,有一点的C语言基础就行了。要点有以下几个: 1. 一定要知道英文缩写的原形,这样寄存器的名字就不用记了。 理解是最好的记忆方法。好的教材一定会给出所有英文缩写的原形。 2. 尽量用形像的方法记忆。 比如TCON和TMOD两个寄存器各位上的功能,教程一般有个图表,你就在学习中不断回忆那个图表的形像。 3. TMOD:定时器/计数器模式控制寄存器(TIMER/COUNTER MODE CONTROL REGISTER) 定时器/计数器模式控制寄存器TMOD是一个逐位定义的8位寄存器,但只能使用字节寻址,其字节地址为89H。其格式为:其中低四位定义定时器/计数器C/T0,高四位定义定时器/计数器C/T1,各位的说明: (1)GATE——门控制。 GATE=1时,由外部中断引脚INT0、INT1来启动定时器T0、T1。 当INT0引脚为高电平时TR0置位,启动定时器T0; 当INT1引脚为高电平时TR1置位,启动定时器T1。 GATE=0时,仅由TR0,TR1置位分别启动定时器T0、T1。 (2)C/T——功能选择位 C/T=0时为定时功能,C/T=1时为计数功能。 置位时选择计数功能,清零时选择定时功能。 (3)M0、M1——方式选择功能 由于有2位,因此有4种工作方式: M1M0 工作方式 计数器模式 TMOD(设置定时器模式) 0 0 方式0 13位计数器 TMOD=0x00 0 1 方式1 16位计数器 TMOD=0x01 1 0 方式2 自动重装8位计数器 TMOD=0x02 1 1 方式3 T0分为2个8位独立计数器,T1为无中断重装8位计数器 TMOD=0x03 单片机定时器0设置为工作方式1为TMOD=0x01 这里我们一定要知道,TMOD的T是TIMER/COUNTER的意思,MOD是MODE的意思。至于每位上的功能,你只要记住图表,并知道每个英文缩写的原型就可以了。 在程序中用到TMOD时,先立即回忆图表,并根据缩写的单词原形理出每位的意义,如果意义不是很清楚,就查下手册,几次下来,TMOD的图表就已经在脑子里了。 8位 GATE位,本身是门的意思。 7位 C/T Counter/Timer 6位 M1 Mode 1 5位 M0 Mode 0 4. TCON: 定时器/计数器控制寄存器(TIMER/COUNTER CONTROL REGISTER) TMOD分成2段,TCON控制更加精细,分成四段,在本文中只要用到高四段。 TF0(TF1)——计数溢出标志位,当计数器计数溢出时,该位置1。 (1)TR0(TR1)——定时器运行控制位 当TR0(TR1)=0 停止定时器/计数器工作 当TR0(TR1)=1 启动定时器/计数器工作 (2)IE0(IE1)——外中断请求标志位 当CPU采样到P3.2(P3.3)出现有效中断请求时,此位由硬件置1。在中断响应完成后转向中断服务时,再由硬件自动清0。 (3)IT0(IT1)——外中断请求信号方式控制位 当IT0(IT1)=1 脉冲方式(后沿负跳有效) 当IT0(IT1)=0 电平方式(低电平有效)此位由软件置1或清0。 (4)TF0(TF1)——计数溢出标志位 当计数器产生计数溢出时,此位由硬件置1。当转向中断服务时,再有硬件自动清0。计数溢出的标志位的使用有两种情况:采用中断方式时,作中断请求标志位来使用;采用查询方式时,作查询状态位来使用。注意记忆方法,理解单词原形,就绝对不会把TF和TR搞混。TF的F也就是溢出Over Flow的F。TR的R就是运行Run。默认是0不运行,当然要置1才运行。 5. STC单片机STC89C52RC定时器延时时间的计算 延时时间要根据晶振频率计算,不同板子可能有所不同。 时钟周期: 1/时钟源,在我现在这块板子上,晶振频率是11.0592M,也就是时钟周期是 1/11059200秒 机器周期: 一般51单片机是12个时钟周期,我的板子也就是 12/11059200秒 单次定时最长时间: 如果是16位的计数器,16位最大值是65535,共可计数65536次。基本的常数一定要记住,还要记住8位最大值是255,共可计数256次,还要记住8位上每位代表的数值。 12 * 65536/11059200 = 0.0711 s,也就是,71 ms内的定时可以单次定时就完成。如果定时时间超过71 ms,就要循环了。 一次定时需要几次机器周期: 计算公式:定时秒数/机器周期 比如我要定时1秒, 1/(12/11059200)= 921600次,16位计数器最大可计数65536次,921600次早就益出了。我们可以每次定时10 ms,循环100次就可以定时1秒了,1 s缩小100百倍就是10 ms, 也就是每次需要计数9216次。 确实计数器初始值: 定时10 ms时,如果计数器从0开始计数,我们就不知道什么时候到了9216次。所以应该计数了9216次,16位计数器最多计数95536次,然后就溢出,一溢出TCON的TF位就会置1,我们只要经常检测TF位就可以知道什么时候完成10ms的定时了。 计算公式:计数器初始值=最大计数次数 - 需要计数次数 如果定时10 ms,计数器的初始值就是 65536 - 9216 计算计数器的高位和低位: 16位的计数器,也就是两个8位组成,8位的最大计数次数是256。所以: 计数器高位 = 初始值/256 计数器低位 = 初始值%6 6.STC89C52RC单片机定时器示例代码:

    时间:2018-04-23 关键词: 定时器 51单片机

  • 单片机面试问题集

     单片机的最小系统?内部的主要结构? 答:最小系统:电源、晶振(为系统提供基本的时钟信号)、复位电路;内部结构:ROM/RAM、计时器、中断、I/O串并行口、总线扩展控制。 RAM和ROM的区别? 答:ROM(只读存储器):它的信息一次写入后只能被读出,而不能被操作者修改或者删除。一般用于存放固定的程序或数据表格。但是,“只读”这个概念有时候可以被一些新特性的器件颠覆。 RAM(随机存储器):它就是我们平时说的内存,主要用来存放各种现场的输入/输出数据、中间计算结果,以及与外部存储器交换信息,或者作堆栈(特点:先进后出,后进先出)用。它的存储单元根据具体需要可以读出或者改写。 两者区别:RAM只能用于暂时存放程序与数据。一旦电源关闭或发生断电,RAM中的数据就会丢失。而ROM中的数据在电源关闭或者断电后仍然会保留下来。 简而言之:相同点它们都是用来存储数据的 不同点存储数据的方式与数据能不能在二次加工不同 单片机I/O口有什么作用?I/0口的驱动能力?上拉电阻与下拉电阻的作用? 答:I/O口最主要的功能用来与外部器件实现数据信息的交互、速度匹配、数据传送方式和增强单片机的负载能力。它在两者之间扮演桥梁的作用,单片机拥有着串行与并行接口。每个种类的单片机的不同并行口也有着各自不同的功能。 单片机输出低电平时,将允许外部器件,向单片机引脚内灌入电流,这个电流,称为“灌电流”,外部电路称为“灌电流负载”。 单片机输出高电平时,则允许外部器件,从单片机的引脚拉出电流,这个电流,称为“拉电流”,外部电路称为“拉电流负载”。 单片机输出驱动能力的问题:每个单个的引脚,输出低电平的时候,允许外部电路,向引脚灌入的最大电流为 10 mA;每个 8 位的接口(P1、P2 以及 P3),允许向引脚灌入的总电流最大为 15 mA,而 P0 的能力强一些,允许向引脚灌入的最大总电流为26 mA;全部的四个接口所允许的灌电流之和,最大为 71 mA。而当这些引脚“输出高电平”的时候,单片机的“拉电流”能力呢?可以说是太差了,竟然不到 1 mA。 结论就是:单片机输出低电平的时候,驱动能力尚可,而输出高电平的时候,就没有输出电流的能力。 综上所述:灌电流负载,是合理的;而“拉电流负载”和“上拉电阻”会产生很大的无效电流,并且功耗大。 设计单片机的负载电路,应该采用“灌电流负载”的电路形式,以避免无谓的电流消耗。 在数字电路中,只有二种状态,要么是高电平,要么是低电平,在通电初期,这些输出状态是不确定的,为了使电路确定状态,必需使用上拉电阻或下拉电阻,使一个原来不确定电平变高的叫上拉电阻,否则就是下拉电阻,上拉电阻就是从电源上接一只电阻到这个状态口上就可以了,(就是把高的电压加到这个点上去,这个点的电位就高了)下拉电阻的接法,从这个状态口接一只电阻到负极(或数字接地),因电路形式与类别不同,当输入端有信号,这种变化会反应到输出口,从输出口得到了一个状态,本来应该完成任务了,但这会儿输入口已没信号了,可输出端还是这个状态(这个人习惯不好,开门后总是不关门,加一只弹簧,(电阻)让它自己关门,)这时候也要用到上下拉电阻,这里有复位的作用。 常见的时钟电路有哪些?为什么要使用PLL? 答:先了解一下什么是时钟电路? 时钟电路就是产生像时钟一样准确运动的振荡电路,任何工作都按时间顺序。用于产生这个时间的电路就是时钟电路。 组成:晶体振荡器、晶震控制芯片和电容组成。 现在流行的串行时钟电路有:DS1302、DS1307、PCF8485等 它们的特点:接口简单、价格低廉、使用方便。 DS1302:具有涓细电流充电能力的电路,主要特点:采用串行数据传输,可为掉电保护电源提供可编程的充电功能,并且可以关闭充电功能。采用普通32.768KHz晶振。 PLL(PhaseLocked Loop):锁相环电路。用来统一整合时脉讯号,使高频器件正常工作。如:内存的存取资料等。PLL用于振荡器中的反馈技术。许多电子设备要正常工作,通常需要外部的输入信号与内部的振荡信号同步。一般的晶振由于工艺与成本原因,做不到很高的频率,而在需要高频应用时,有相应的器件VCO,实现转成高频,但不稳定,故利用锁相环路就可以实现稳定且高频的时脉冲讯号。 什么是时脉:指同步电路中时钟的基础频率,它以(若千次周期每秒)来度量,单位是(Hz) 总之:PLL可以同步频率,相位正交。倍频、变频。 单片机的寻址方式有哪些? 答:80C51有七种寻址方式: 1、立即寻址,寻址空间为ROM; 2、直接寻址,寻址空间为片内RAM的低128B和特殊功能寄存器; 3、寄存器寻址,寻址空间为A、B、DPTR、CY、通用工作寄存器等; 4、寄存器间接寻址,片内RAM低128B、片外RAM; 5、相对寻址,寻址空间为ROM; 6、变址寻址,寻址空间为ROM; 7、位寻址,寻址空间为片内RAM低128B的位寻址区的128个位,其字节地址为20H~2FH;以及部分可以位寻址的特殊功能寄存器。 参考:AT89C51单片机能直接认识和执行的机器指令有255条,有7种寻址方式,即立即寻址、直接寻址、寄存器寻址、寄存器间接寻址、变址寻址、相对寻址和位寻址。 1. 直接寻址: 指令中直接给出参与操作的数据的地址,该地址一般用direct表示。 汇编指令:MOV A,direct 该指令的功能是将片内RAM地址direct单元中的内容(参与操作的数据)传送到累加器A中,双字节指令。 2. 立即寻址: 指令中直接给出参与操作的数据,称立即数,用data表示。在汇编语言中,为标明立即数,为data加前缀”#”。立即数可以是8位和16位二进制数,分别用#data和#data16表示。 汇编语言指令:MOV A,#data 该指令将立即数data传送到累加器A中,双字节指令。 3. 寄存器寻址: 参与操作的数据存放在寄存器中,汇编指令中直接以寄存器名来表示参与操作的数据地址,寄存器包括工作寄存器R0~R7、累加器A、AB、数据指针DPTR和位运算寄存器C。 汇编语言指令:MOV A,Rn ;n=0~7 该指令将Rn中的内容传送到累加器A中,单字节指令。 4. 寄存器间接寻址: 寄存器间接寻址为二次寻址,第一次寻址得到寄存器的内容为(Ri)或(DPTR),第二次寻址是将第一次寻址得到的寄存器内容作为地址,并在其中存、取参与操作的数据。汇编语言中,寄存器前缀@是寄存器间接寻址的标志,有@Ri、@DPTR等。 汇编语言指令:MOV A,@Ri ;i=0、1 该指令是将Ri中的内容作为地址,再将该地址中的内容传送到累加器A中,单字节指令。 5. 变址寻址: 间接寻址由两个寄存器提供。若由A、PC提供,在汇编语言指令中寻址地址表示为@A+PC;若由A和DPTR提供,在汇编语言指令中寻址地址为@A+DPTR。其中,PC或DPTR被称为基址寄存器,A被称为变址寄存器,基址与变址相加为16位无符号加法。若变址寄存器A中内容加基址寄存器DPTR(或PC)中内容时,低8位有进位,则该进位直接加到高位,不影响进位标志。因变址寻址指令多用于查表,故常称为查表指令。 汇编语言指令:MOVC A,@A+DPTR 该指令将DPTR中的内容加上A中的内容作为地址,再将该地址中的内容传送到累加器A中,单字节指令。 6. 相对寻址: 相对寻址是以相对寻址指令的下一条指令的程序计数器PC的内容为基值,加上指令机器代码中的“相对地址”,形成新的PC值(要转移的指令地址)的寻址方式。指令机器代码中“相对地址”指的是用一个带符号的8位二进制补码表示的偏移字节数,其取值范围为-128~+127,负数表示向后转移,正数表示向前转移。 若(PC)表示该指令在ROM中的首地址,该指令字节数为2,执行时分两步操作:(PC)←(PC)+2,(PC)←(PC)+相对地址。第一步完成后,PC中的值为该指令的下一条指令的首地址;第二步完成后,PC中的内容(PC)为转移的目标地址。所以,转移的目标地址范围是该相对寻址指令的下一条指令首址加上-128~—+127字节的地址。 汇编语言指令:SJMP rel 汇编语言相对寻址指令中的”rel”往往是一个标号地址,表示ROM中某转移目标地址。汇编软件对该汇编语言指令进行汇编时,自动算出“相对地址”并填入机器代码中,应将”rel”理解为“带有相对意义的转移目标地址”。 Rel=(PC)+相对寻址指令字节数+相对地址 其中,(PC)为该指令所在ROM中的首地址。 7. 位寻址: 参与操作的数据为“位”,而不是字节,是对片内数据存储器RAM和SFR中可位寻址单元的位进行操作的寻址方式。 汇编语言指令:ANL C,bit 该指令将bit(位地址)中的内容(0或1)与C中的内容进行与操作,再将结果传送到PSW中的进位标志C中。 什么是时钟周期?机器周期?指令周期?它们之间的关系? 答:1、时钟周期又叫做振荡周期;单片机内部时钟电路产生(或外部时钟电路送入)的信号周期,单片机的时序信号是以时钟周期信号为基础而形成的,在它的基础上形成了机器周期、指令周期和各种时序信号。定义为时钟脉冲的倒数(可以这样理解:时钟周期就是单片机外接晶振的倒数,例如:12M的晶振,它的时钟周期就是1/12us),是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作。 2、计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一个阶段完成一项工作。例如:取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一项基本操作所需要的时间成为机器周期。一般情况,一个机器周期由若干个S周期(状态周期)组成。机器周期是单片机的基本操作周期,每个机器周期包含S1、S2、……S6这6个状态,每个状态包含两拍P1和P2,每个拍为一个时钟周期(振荡周期)。因此,一个机器周期包含12个时钟周期。依次可表示为S1P1、S1P2、S2P1、S2P2……、S6P1、S6P2。 3、指令周期:计算机从取一条指令开始,到执行完该指令所需要的时间称为指令周期。不同的指令,指令长度不同,指令周期也不一样。但指令周期以机器周期为单位,指令不同,所需的机器指令也不同。 51单片机指令根据指令长度与执行周期分为: 1) 单字节单周期指令 2) 单字节双周期指令 3) 双字节单周期指令 4) 双字节双周期指令 5) 三字节双周期指令 6) 一字节四周期指令 总结:时钟周期是最小单位,机器周期需要1个或者多个时钟周期,指令周期需要1个或者多个机器周期;机器周期是指完成一个基本操作的时间。指令周期是CPU的关键指标,指取出并执行一条指令的时间。一般以机器周期为单位,分单指令执行周期、双指令执行周期等。机器周期是完成一个基本操作的时间单元。时钟周期是CPU的晶振的工作频率的倒数。 单片机有哪些接口,各模块有哪些特性及应用环境? 答:接口电路——用于衔接外设与总线,实现存储空间扩展、I/O口线扩展、类型转换(电平转换、串并转换、A/D转换)、功能模块、通信扩展、总线扩展等。 外围设备——工作设备,连接在接口电路上,主要有输出设备和输入设备。 看门狗相关问题: 看门狗的原理? 答:工作原理:在系统运行以后就启动看门狗计数器,此时看门狗就开始自动计时,如果达到一定的时间还不去给它进行清零,看门狗计数器就会溢出从而引起看门狗中断,造成系统的复位。 为什么会溢出呢? 因为看门狗是一个计数器,而计数器位数有限。能够装的数值也就有限(比如8位的最多装256个数,16位的最多装65536个数),从开启看门狗那刻起,它就开始不停的数机器周期,数一个机器周期就计数器加1,加到计数器盛不下了(这就是溢出)就产生一个复位信号,重启系统。 看门狗分为软件看门狗和硬件看门狗,在什么情况下软件看门狗失效? 答:硬件看门狗是利用一个定时器电路,其定时输出连接到电路的复位端,程序在一定时间范围内对定时器清零,因此程序正常工作时,定时器总不能溢出,也就不能产生复位信号。如果程序出现故障,不在定时周期内复位看门狗,就使得看门狗定时器溢出产生复位信号并重启系统。 软件看门狗原理上一样,只是将硬件电路上的定时器用处理器的内部定时器代替,这样可以简化硬件电路设计,但是在可靠性方面不如硬件定时器。 1、系统内部定时器自身发生故障看门狗就会失效(当然可以通过双定时器相互监视,成本高); 2、中断系统故障导致定时器中断失效。 3、整个程序死机。主程序出现异常。 什么时候喂狗?怎么喂狗?喂狗的注意事项? 答:在实际的单片机应用系统中,到底选用哪种型号的看门狗,采用何种喂狗方式和看门狗连接方式以及在编程中喂狗命令应该放在程序中什么位置,这要根据现场干扰源的特点、已采用的干扰措施、单片机硬件资源和软件结构特点以及对单片机系统的可靠性等具体情况而定。 看门狗一般应用到程序的那些位置? 答: 一些重要的程序,必须让它一直跑着;而且还要时时关心它的状态——不能让它出现死锁现象。(当然,如果一个主程序会出现死锁,肯定是设计或者编程上的失误。首要做的事是Debug。)但如果时间紧迫可以用软件看门狗,暂时应急。 喂狗命令放置位置: 定时中断服务子程序中; 主程序中. 复位相关问题: 复位时单片机有什么动作? 答:主要做的就是初始化每个寄存器,包括最重要的PC指针,不包括RAM,然后单片机从复位地址开始执行程序。 必须使RST引脚(9)加上持续两个机器周期(即24个振荡周期)的高电平。单片机就执行复位操作。如果RST持续为高电平,单片机就处于循环复位状态。 单片机有哪些复位源,什么情况下会导致各个复位源 答: 6个复位源 TM4C123GH6PM微控制器有6个复位源: (1)上电复位(POR); (2)外部复位输入引脚有效(RST); (3)掉电检测可以用于由以下任一事件引起:① 低于BOR0,触发值是BOR0的 最高电压值;② 低于BOR1,触发值是BOR1的最高 电压值。 (4)软件启动复位(利用软件复位寄存器); (5)违反看门狗复位条件; (6)MOSC故障。 STM8S共有9个复位源: NRST引脚产生的外部复位 上电复位(POR) 掉电复位(BOR) 独立看门狗复位 窗口看门狗复位 软件复位 SWIM复位 非法操作码复位 EMS复位:当一些关键的寄存器被破坏或错误加载时产生的复位 所有的复位源最终都作用于NRST管脚,并在复位过程中保持低电平。复位入口向量在内存映射中位于固定的地址6000h。 复位的两种启动方式? 答:1、上电复位:要求接通电源后,自动实现复位操作。 2、按钮复位:要求接通电源后,单片机自动复位,并且在单片机运行期间,使用开关也可以实现复位。 复位存在哪些问题? 答:采用上电复位:复位信号上电过程中有掉电现象,可能会造成逻辑方面的错误导致相关芯片复位时间不够、二次复位等、从而不能够正常工作起来。原因:产生的复位信号给了几个芯片用,导致这个复位信号的负载压力比较大,驱动能力不够从而往下调电,此现象常常出现在RC复位上。 复位信号上电前有毛刺与复位信号上电时有毛刺,可能会造成逻辑方面的错误导致相关芯片复位时间不够。方案:加大上拉电阻的阻值。 复位电路有哪些?那种比较好? 答:单片机复位电路主要有四种类型: (1)微分型复位电路: (2)积分型复位电路: (3)比较器型复位电路: 比较器型复位电路的基本原理。上电复位时,由于组成了一个RC低通网络,所以比较器的正相输入端的电压比负相端输入电压延迟一定时间.而比较器的负相端网络的时间常数远远小于正相端RC网络的时间常数,因此在正端电压还没有超过负端电压时,比较器输出低电平,经反相器后产生高电平.复位脉冲的宽度主要取决于正常电压上升的速度.由于负端电压放电回路时间常数较大,因此对电源电压的波动不敏感.但是容易产生以下二种不利现象: 1)电源二次开关间隔太短时,复位不可靠: 2)当电源电压中有浪涌现象时,可能在浪涌消失后不能产生复位脉冲。 为此,将改进比较器重定电路,可以利用数字逻辑的方法和比较器配合,设计的比较器重定电路。此电路稍加改进即可作为上电复位和看门狗复位电路共同复位的电路,大大提高了复位的可靠性。 (4)看门狗型复位电路. 看门狗型复位电路主要利用CPU正常工作时,定时复位计数器,使得计数器的值不超过某一设定的值;当CPU不能正常工作时,由于计数器不能被复位,因此其计数会超过某一值,从而产生复位脉冲,使得CPU恢复正常工作状态。此复位电路的可靠性主要取决于软件设计,即将定时向复位电路发出脉冲的程序放在何处是最优的设计。一般设计,将此段程序放在定时器中断服务子程序中。然而,有时这种设计仍然会引起程序走飞或工作不正常。原因主要是:当程序"走飞"发生时,定时器初始化以及开中断之后的话,这种"走飞"情况就有可能不能由Watchdog复位电路校正回来.因为定时器中断一真在产生,即使程序不正常,Watchdog也能被正常复位.为此提出定时器加预设的设计方法.即在初始化时压入堆栈一个地址,在此地址内执行的是一条关中断和一条死循环语句.在所有不被程序代码占用的地址尽可能地用子程序返回指令RET代替.这样,当程序走飞后,其进入陷阱的可能性将大大增加.而一旦进入陷阱,定时器停止工作并且关闭中断,从而使Watchdog复位电路会产生一个复位脉冲将CPU复位.当然这种技术用于实时性较强的控制或处理软件中有一定的困难。 由此可见,四种复位电路中,看门狗型复位电路较其他三中复位电路更加适合。 堆栈相关问题: 堆栈的原理?过程怎么操作? 答:单片机RAM中,常常会指定一个专门的区域来存放某些特别的数据,它遵循先进后出、后进先出的原则。这个RAM区就是堆栈。堆栈是一种数据结构(数据项按序排列:堆,顺序随意。栈,后进先出)。使用一个称作堆栈指针的专用寄存器指示前的操作位置,堆栈指针总是指向栈顶。 操作过程: 1) 堆栈的建立(初始化) 2) 参数入栈(push) 3) 参数出栈(pop)(后进先出) 堆栈应用在什么地方?存在什么问题?怎么解决? 答:在80C51单片机中,堆栈在子程序调用和中断时会把断点地址自动进栈和出栈。进栈和出栈的指令(PUSH、POP)操作可用于保护现场和恢复现场。由于子程序调用和中断都允许嵌套,并可以多级嵌套,而现场的保护也往往使用堆栈,所以一定要注意给堆栈以一定的深度,以免造成堆栈内容的破坏而引起程序执行的“跑飞”。 堆栈指针SP在80C51中存放当前的堆栈栈顶所指存储单元地址的一个8位寄存器。80C51单片机的堆栈是向上生成的,即进栈时SP的内容是增加的;出栈时SP的内容是减少的。 系统复位后。80C51的SP内容为07H。若不重新定义,则以07H为栈底,压栈的内容从08H单元开始存放。但工作寄存器R0~R7有4组,占有内部RAM地址为00H~1FH,位寻址区占有内部RAM地址为20H~2FH。若程序中使用了工作寄存器1~3组或位寻址区,则必须通过软件对SP的内容重新定义,使堆栈区设定在片内数据RAM区中的某一区域内(如30H),堆栈深度不能超过片内RAM空间。 学习堆栈的作用? 答:在片内RAM中,常常要指定一个专门的区域来存放某些特别的数据,它遵循顺序存取和后进先出(LIFO/FILO)的原则,这个RAM区叫堆栈。 1.子程序调用和中断服务时CPU自动将当前PC值压栈保存,返回时自动将PC值弹栈。 2.保护现场/恢复现场 3.数据传输 也就是说,堆栈是进入中断的时候用到的,单片机一旦遇到中断请求,就会去处理中断,处理完后再回来处理主程序,这样就涉及到了一个问题,单片机要保存中断之前的信息,以便处理完后能够回到主程序中,单片机会在响应中断前,把单片机现在的指针地址(也就是处理完要返回的地址),以及一些必要的数据压入堆栈(没有这些数据,单片机处理完中断后就无法确定主程序的状态)。 中断相关问题: 中断的响应过程?优先级? 答:1、响应过程: 1)根据响应的中断源的中断优先级,使相应的优先级状态触发器置1; 2) 执行硬件中断服务子程序调用,并把当前程序计数器PC的内容压入堆栈,保护断点,寻找中断源。 3) 清除相应的中断请求标志位(串行口中断请求标志RI和TI除外); 4) 把被响应的中断源所对应的中断服务程序的入口地址(中断矢量)送入PC,从而转入相应的中断服务程序。 5) 中断返回,程序返回断点处进行执行。 2、优先级:中断的优先级有两个:查询优先级与执行优先级 查询优先级:查询优先级是不可以更改和设置的 1)系统默认的优先级(逻辑上): 外部中断0 > 定时器中断0 > 外部中断1 > 定时器中断1 > 串行中断 2)由IP寄存器来决定优先级。当IP对应位置为1时,该中断级别提高。同为1是按默认级别 中断嵌套的好处与坏处? 答:中断嵌套:CPU在处理级别较低的中断过程中,出现了级别较高的中断请求。CPU停止执行低级别中断,执行高级别的中断处理程序后,再接着执行低级别的未被处理完的中断程序。 使用中断嵌套可以使高优先级别的中断得到及时的响应和处理。 1) CPU与外部设备并行工作 2) 能够处理例外事件 3) 实现实时处理 4) 实现人机联系 5) 实现用户程序与操作系统的联系 6) 实现多道程序并行执行 7) 在多处理机系统中,实现处理机之间的联系 坏处:高级别的中断一直持续的话会影响低级别中断的处理。 中断向量表的理解 答:中断源的识别标志,可用来形成相应的中断服务程序的入口地址或存放中断服务程序的首地址称为中断向量。把所有的中断向量集中起来,按中断类型号从小到大的顺序存放到存储器的某一区域内,这个存放中断向量的存储区叫做中断向量表,即中断服务程序入口地址表。 中断在单片机中起到什么作用? 答:中断能实现快速的CPU与慢速的外设同步工作,实现数据传送、故障检测与处理、人机联系、多机系统、多道程序分时操作、实时信息处理等。 其它: 什么是临界段,哪些情况下会存在临界段的问题,如何进行临界段保护。 答:1.临界段代码,也叫临界区,是指那些必须完整连续运行,不可被打断的代码段。 2. ①读取或者修改变量(特别是用于任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。 ②调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。总之,对于临界段要做到执行时间越短越好,否则会影响系统的实时性。 3.中断处理程序和任务都会访问的临界段代码,需要使用关中断的方法加以保护;仅由任务访问的临界段代码,可以通过给调度器上锁的方法来保护。 什么是可重入型函数,使用时需要注意哪些问题 答:可重入函数 不为连续的调用持有静态数据。 不返回指向静态数据的指针;所有数据都由函数的调用者提供。 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。 如果必须访问全局变量,记住利用互斥信号量来保护全局变量。 绝不调用任何不可重入函数。 注意事项: 1 :编写可重入函数时,应注意局部变量的使用(如编写C/C++ 语言的可重入函数时,应使用auto 即缺省态局部变量或寄存器变量) 说明:编写C/C++语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。 2 :编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P 、V 操作)等手段对其加以保护 说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。 专用词汇解释: VCC:C=circuit 表示电路的意思, 即接入电路的电压; VDD:D=device 表示器件的意思, 即器件 内部的工作电压; 对于数字电路来说,VCC是电路的供电电压,VDD是芯片的工作电压(通常Vcc>Vdd),VSS是接地点;有些IC既有VDD引脚又有VCC引脚,说明这种器件自身带有电压转换功能。 VEE:发射极电源电压, Emitter Voltage, 一般用于 ECL 电路的负电源电压. VSS:S=series 表示公共连接的意思,通常是指电路公共接地端电压。 VPP:不同芯片对Vpp的定义稍有不同,比如电压峰峰值,单片机中Vpp多数定义为编程电压 在场效应管(或COMS器件)中,VDD为漏极,VSS为源极,VDD和VSS指的是元件引脚,而不表示供电电压。一般来说VCC=模拟电源,VDD=数字电源,VSS=数字地,VEE=负电源

    时间:2018-04-23 关键词: 面试题 单片机

发布文章