个人成就
- 发布了50篇内容
- 获得了4次赞同
- 获得了3次收藏
个人简介
擅长领域
暂时没有设置哦~
-
8脚51单片机DIY时间显示+闹钟技术分享(四)
软件设计
前情提要:首先感谢大家能持续关注我的DIY闹钟的实现过程,前面分享了我的PCB设计过程,接下来就是拿到PCB板焊接程序调试了,开始的想法到慢慢实现,真的收获多多啊,接下来我还是主要分享我的程序设计思路,以及实现过程的坎坷。希望能给大家带来不一样的收获。学习总归是好的,变成自己的才是王道哈。
具体思路和程序部分我也会陆续更新出视频,感兴趣的可以关注下哈~
接下来就是我当时的设计初衷了,想要实现的功能,那就可以分模块来实现了。先把思路捋顺,事情会变得事半功倍的,还不会容易出错,到最后的调试也会变得轻松许多了。
我们知道程序设计就是数据结构加算法,首先要有算法才能实现,那么算法的表示方法可以很多,主要我还是倾向于用流程图实现,就很直观了。那么接下来我会用流程图和实际程序部分来表述我的程序设计。
1、程序主要实现的功能:
这就是在我最初设想下需要实现的功能模块。具体再根据想法一步步实现。蜂鸣器是要唱歌的,那么我放在了定时器中断内部处理了,后面讲原因哈。
2、首先是初始化部分,一起来看下都需要初始化什么,
首先就是对单片机的初始化,比如平常我们用到的时钟配置等等。这个呢就是要研读我们的单片机数据手册了,不能想当然,选取了单片机数据手册要反复看多次呢,每款都是不一样的,除非你经常用一款,那么就方便多了,由于这款我是第一次用,所以还是要仔细些好。
先看下对I2C管脚的配置,配置模式如下,
再有就是定时器的初始化了。后面的部分就是对每个模块的全局变量(所有全局变量我一般定义成结构体)初始化了。模块化逻辑清晰,方便扩展的。这里主要说下我的设计思路,具体实现可以关注视频部分哈。
3、接下来就是每个功能模块具体的实现流程了,
先看下显示处理模块,显示处理先来分析下当时需要实现的功能,
一是正常时间的显示;
二是对时间的设置,有小时和分钟的设置,会有闪烁;
三是对报警时间的设置,有小时和分钟的设置,快闪(区别于时间设置);
最后就是把不同模式下的数据显示出来。
这样显得很清晰了,那么这里就可以用一个模式变量来进行判断当前状态就可以了
4、继续看下时间处理模块都有哪些功能需要实现,
首先要读取时钟芯片的当前时间,
二是判断是否是半点,蜂鸣器叫一声状态置位
三是判断是否是整点,蜂鸣器叫两声状态置位
四是读取报警标志,如果是当前报警时间,蜂鸣器唱歌状态置位。
4、最后就是按键处理模块,就是一个按键扫描和按键处理,是不是看上去挺简单的,最开始就跟大家介绍过这款数码管驱动芯片带按键处理的,芯片强大的不知道大家有没有看看呢。下面我们一起来看看特点吧,
是不是真的很强大,按键也处理了,那么对于我们应用来说就直接扫描就够了,处理起来也方便太多了。
接着看下按键处理,也就是对按键读取过来的实际应用了,这个按键我设计了四个,一个模式键,一个加值键,一个减值键,一个确认键。上图吧:
其实处理一定要有条理性,模块化,这样真的调试也会清晰很多。
5、最后就是中断处理部分了,对于一些显示和按键的计数处理,定时器我配置的1ms;前面说蜂鸣器处理放在了定时器内部,这里说下我的原因,其实开始我是放在主循环的,后面加了蜂鸣器唱歌的部分,发现调调总是不对,这个确实让我调试了不少时间,后来经过示波器测试发现主循环时间过长,导致唱歌时间无法保证。所以我才把这个蜂鸣器的处理放在中断里面处理,具体这个蜂鸣器模块处理时间不长。话说这个是调试最多的部分了,一直纠结于音调和频率的问题,后来实际测试才发现是主循环长导致。
6、下面来说下这个蜂鸣器模块,也是通过模式处理的,通过全局变量的当前模式处理不同的响声。因为我选用的无源蜂鸣器,那么就要看下它的响的频率,也就是PWM周期配置。
对于唱歌的话还要去研究下歌曲的调、节拍、延长音等,一度觉得自己都是音乐的研究人了,哈。当然有同事的帮忙快速学了植入到程序内,这个也是难啃的骨头啊。如果有人对这块感兴趣可以看我的详细视频来了解下,话说不学音乐也不影响实现蜂鸣器的响啊,这个其实也是在研究单片机的PWM功能,也算长进不少啊。
这个就是我最终的结果了。
做到这里程序设计的思路都分享完了,以至于我的闹钟DIY设计也实现了,其实纵观看下只要有条理,还需要细心,然后就都可以实现。大家看下我的设计有没有值得借鉴的地方或者有需要改进的,随时欢迎沟通交流。实现方法有很多,或者大家有更多更好的方法呢,欢迎来交流啊。后面会陆续更新其他小玩意的设计过程,感兴趣的持续关注吧。
-
8脚51单片机DIY时间显示+闹钟技术分享(二)
原理图设计
延续前篇,感谢大家能关注我的实现过程,废话不多说,接下来就是跟大家分享我的原理图设计过程了,首先呢就是我前面说到了DIY效果所以选择用LED来组成数码管,所以我选择用红灯(压降低)来实现,具体做出来就是下面这样的效果了:
是不是感觉还不错呢,主要显示小时和分钟。
因为我选择USB供电,先把供电端设计完成,供电进来我这里加了个保险丝(9V/200mA---大小根据自己实际电路选择),保护整个电路板。
前面设计的时候说到了的模块,我这里再来罗列一下,然后可以通过模块一点点讲实现了。有单片机模块,按键显示模块,时钟芯片模块,蜂鸣器模块。我一个一个来跟大家讲下我的实现过程和感觉需要注意的设计要点。
单片机模块:
电源端就是一个滤波电容一个储能电容。
一个蜂鸣器输出端,其实时钟芯片有触发闹钟时间到的功能,这里我就先把端口连上了,实际后来我实现的时候是通过I2C口读取寄存器获取的。
后面就好理解了,就是两个口是I2C口,另外两个是下载通讯口了,连接出来加个排针就好了。这里我都加了一个10R的电阻是防震荡的作用。到这里呢这个单片机模块部分就完成了,是不是很简单。继续吧。
按键显示模块:
这个就是我前面特别推的一款非常实用的按键显示芯片了。每个段都加了个电阻做限流用的,可以根据实际情况(LED灯的亮度)来调整这个电阻的。这里我们就看到了对于显示和按键我们只用了单片机的I2C两个接口,是不是很强大了。
其实这个芯片还有很多内部处理,比如按键消抖这些,很好了。前面如果下载了我的文章附件的可能大概浏览了下,是不是真的很好呢,希望对你们也有些借鉴。
对于这里的电源端我也加了滤波电容和储能电容。
按键部分我也贴图看下吧,
按键就是用的段管脚,这里这个4.7K的电阻就是一个限流作用了,这里我设计的是共阴使用。
接下来我们看下这个时钟芯片模块部分,还是先贴图看看我的设计吧,然后再看我的设计思路,
先看左边部分,我们看看这个ALARM管脚是不是在单片机管脚的那里连接着呢,就是这个口,那里也说了我最终闹钟实现是通过I2C读取寄存器实现的,这里就不多讲了。这里这个10K电阻是上拉电阻,这个端口是开漏输出,电阻选大点也是为了功耗小点。I2C信号端要加一个4.7K的上拉电阻(这个的话就是看的I2C标准了),再接着看下这个时钟芯片是加外部晶振的,这个是根据手册看的哦。两个电容就是起震需求的电容了。再接着看下供电端,这里我加了个电池,就是在5V不供电的时候,需要电池来给时钟芯片供电,确保时间是对的。所以这里我用了一个二合一二极管,就是为了电池只给时钟芯片供电,防止那个5V的电给电池充电,这里电池我选择的是不可充电的啊。电源端的电容就是滤波用的啊。
到这里时钟芯片部分的设计就完成了,是不是也不难,挺清楚的,如果这部分大家有什么更好的建议欢迎多多交流啊。
接着大家看下我的蜂鸣器部分。还是先上图,再给大家说下我的思路呢,
对于蜂鸣器,我选用的是无源压电式蜂鸣器,我们来看下思路,电源进来,上面我加了一个10R的限流电阻,下面这个R15的作用就是为蜂鸣器放电用的,这个BEEP管脚给一个频率输出到蜂鸣器,让它响出不同的声音,所以这里我用了一个三极管,这个电阻R17的作用就是限流了,下面这个R18就是三极管结电容加速关断的作用了。这样解释一下是不是还是很好理解我的思路的?
做到这里所有思路都讲完了,其实纵观看下还是很容易实现的。原理图设计就这样完成了,大家看下我的有没有什么需要改进的地方呢?实现方法有很多,或者大家有更多更好的方法呢,欢迎来交流啊。后面会陆续更新,下面一篇会展现我的PCB设计过程,感兴趣的持续关注吧。
-
别再说你的单片机RAM不够用了,来看看这个吧...
当我们写代码的时候,会用到很多变量,如果随意的定义变量,比如写了N多个“unsigned char/int X;”那么代码可能会显的很乱,自己拐回头看的时候都晕掉了,那么这个时候我们可以构造一个复杂的数据类型-结构体类型,对代码中出现的变量进行类别的划分,用构造的结构体类型定义结构体变量,在写or看代码的时候,只要看到这个结构体,就能大致的知道其实现功能,这样看起来就神清气爽了,可读性大大提高。
我们定义的结构体变量,如果没有特殊规定的话是存储在RAM中的,单片机的RAM资源是有限的,那这个结构体变量在RAM中占的空间大小就是我们需要关注一个问题了,它真的像你想的那么“单纯”吗?接下来我们一起来看看吧!
在看下面的图之前,我们说一个前提,在STM32单片机这个32位系统中,signed/unsigned int 占4个字节,signed/unsigned short int 占2个字节 signed/unsigned char 占1个字节,我们称这些为基本数据类型。Size = Sizeof(Test);这个函数是求取这个结构体变量Test所占内存的大小,并返回给Size。
请看上图,我们使用基本数据类型构造了3个复杂的结构体数据类型,仔细看会发现,这3个数据类型的成员可是不大一样的,我们来看第一个Test,这个数据类型总共占4+4=8个字节,这个很好理解,那第二个Test1,占空间大小按道理来说应该是1+4 = 5个字节,但是为什么还是8呢,第三个Test2,占空间大小应该是1+1+4=8,为什么还是8呢?
这个里面就涉及到了结构体对齐,所有的成员在分配内存时都要与所有成员中占内存最多的基本数据类型所占内存空间的字节数对齐。假如这个字节数为 N,那么对齐的原则是:理论上所有成员在分配内存时都是紧接在前一个变量后面依次填充的,但是如果是“以 N 对齐”为原则,那么,如果一行中剩下的空间不足以填充某成员变量时,即剩下的空间小于某成员变量的数据类型所占的字节数,该成员变量在分配内存时另起一行分配。如图3,4:
通过上面的实际测试,我们得出,在构造结构体复杂数据类型的时候,成员变量的排放一定要注意顺序,遵守排放原则,否则就会白白浪费你的空间,掌握好排放原理,能大大提高你的空间利用率。比如我们构造如图5的结构体类型,它依然还是占8个字节。
文末再给大家出个问题,大家看看下面我们构造的数据类型,它们分别占的空间是多大呢?
-
聊一聊内存指针操作
在嵌入式系统中,对内存地址的操作是一个重要的方面,从广义上讲,嵌入式系统的地址空间可以分成以下三种类型:
l 系统的内存
l 处理器内部的寄存器映射
l 处理器外部部件的内存映射
无论哪种内存,一般都映射到处理器的内存空间中。在x86系统中,分为内存和I/O映射两种内存;在ARM体系中,全部的地址都在32位的内存空间中,所有的操作都是对32位地址空间内存的操作。
从编程的角度看,嵌入式系统和PC系统的软件设计的一个重要的区别即在于嵌入式系统更重视对硬件的操作。而对硬件的操作需要通过操作内部寄存器和外围部件内存映射的地址实现,其实现方式都是通过对内存读、写两种操作。
在汇编语言中,各种处理器都有对内存的不同的寻址方式读写内存。在高级语言中,C语言是唯一可以进行内存操作的语言,C语言对内存的操作主要需要通过指针来完成。
1、使用指针操作内存
在C语言中,指针是一种非常重要的数据类型。使用指针变量可以表示各种数据结构,能很方便地使用数组和字符串,并能像汇编一样处理内存地址。指针的本质就是一个地址,在32位的系统中,指针是一个32位的无符号整数。指针可以用一个变量来表示,变量的指针实际上就是变量的地址。存放变量地址的变量是指针变量。一个指针变量的值就是某个变量的地址或称为某变量的指针。
一个简单的指针应用如下所示:
int a;
int *p = &a;
这个例子表示,整型指针型变量p指向a的地址,此时对*p的操作等同于对a的操作。
使用指针可以指向一个变量,也可以指向一个由malloc函数分配的内存,例如:
void *p = malloc(1024);
系统分配1024字节的内存,然后让变量p指向这块内存,即p的值是这1024字节的连续内存的地址。在程序中就可以通过p来操作这块内存区域。在内存使用完成后,需要使用free函数讲内存释放。
free(p);
在嵌入式系统中的程序开发中,指针的值除了以上的两种形式(从系统内存分配或者指向变量)以外,还可以使用绝对的数值。这是由于在嵌入式系统中,外设寄存器和外部部件的内存映射的地址空间可能都是固定的,因此可以使用指针来处理他们。
例如,如果需要在地址0x0040处写入一个字节的数据0xf0,可以使用如下的程序:
unsigned char *p = (unsigned char *)0x0040;
*p = 0xf0;
这个程序定义了一个指向0x0040地址的字节型的指针,然后向该地址写入数据0xf0。
上面的程序等同于:
*(unsigned char *)0x0040 = 0xf0;
所以说不使用指针变量也可以对实际的地址操作。读内存的程序与之类似,可以使用指针变量或者直接使用地址得到内存中的数据。
2、指针的类型
前面的程序在内存的一个指定的地址处写入一个字节(8位)的数据。如果需要写入两个字节(16位)的数据,需要改变指针类型。例如:同样向地址0x0040处写入两个字节的数据0x0f0f,需要使用如下的语句:
*(unsigned short*)0x0040 = 0x0f0f;
在这个语句中,使用unsigned short而不使用unsigned char,short在C语言中代表16位的整数。
如果写入4个字节(32位)的数据,则需要使用一下的程序:
*(unsigned long*)0x0040 = 0xf0f0f0f0;
在这个语句中,使用unsigned long,long在C语言中代表32位的整数。
在32位的系统中,一般编译器认为int代表是32位的整数,等同于long,所以习惯使用int代替long作为内存操作的数据类型。
注意:指针的类型决定了使用指针进行读写操作时每次读写字节的数目。
3、指针的增量
在对指针变量的操作中,有时需要对指针变量进行加减运算。例如:
unsigned char *p = (unsigned char *)0x0040;
P++;
*p = 0xf0;
这段程序的含义是向地址0x0041的字节处写入数据0xf0。
指针加减运算的含义是:指针的单位增量(或减量)等于指针类型所占的内存量。
对指针进行增量操作的使用,增加的单位是以指针类型的大小:char类型的增量表示增加1字节的内存,short表示增加2字节的内存,long和int表示增加4字节的内存,这些工作是编译器根据指针的类型自动完成的。
总结:对指针进行加减运算的时候,它的变化量与指针的类型有关。
4、指针的类型转换
在C语言中,指针的类型可以在使用的时候进行转换。指针的本质是一个地址,在32位系统中,指针就是一个32位无符号的整数。因此,各种指针都可以相互转化,而且指针在转换过程并没有任何实质性的变化,只是告诉编译器,目前的指针指向何种的内存区域。
在嵌入式系统中,处理器的片内设备一般都会映射到处理器的地址空间中。这些寄存器有可能是32位的,有可能只有8位,这时就需要使用C语言中不同类型的指针。
总结:指针的本质是一个无符号的整数,各个类型的指针都可以进行相互转换。
-
USB的拓扑结构
大家好!我是张飞实战电子蔡琰老师,今天给大家分享USB的拓扑结构。
我们经常使用USB,对USB多少了解呢?本篇文章我们一起来学习一下USB的拓扑结构和数据通信原理。
USB是一种主从结构的系统,主机叫做Host,从机叫做Device,通常所说的主机具有一个或者多个USB主控制器(Host controller)和跟集线器(root hub),主控制器主要负责数据处理,而跟集线器则提供一个连接主控制器和设备之间的接口和通路。另外,还有一类特殊的USB设备-USB集线器,我们也叫USB HUB,它可以对原有的USB口的数量上进行扩展,就可以获得更多的USB口。但是需要注意的是集线器只能扩展处更多的USB口,而不能扩展出更多的带宽,带宽是共享一个USB主控制器的。
USB HUB
通常,PC上有多个USB主控制器和多个USB口,每个主控制器下有一个跟集线器,跟集线器下面通常具有一个或者几个USB口,当你有多个不同的USB设备都需要较大的数据带宽时,可以考虑将他们分别接到不同的主控制器的跟集线器上,以避免带宽不足。
USB的数据交换只能发生在主机与设备之间,主机与主机之间,设备与设备之间不能直接互联和交换数据。为了在物理上区分主机和设备,使用了不同的插头和插座,所有的数据都由主机主动发起,而设备只是被动的负责应答,例如,在读数据时,USB先发出命令,设备收到该命令后,才返回数据。
USB OTG比普通的4线USB多了一条ID识别线,用来表明它是主机还是设备,它可以在主机和设备之间切换角色,这样就实现了设备与设备的链接,增大了USB的使用范围。但是需要注意的是依然没有脱离主从关系,两个设备之间必须要有一个作为主机,一个作为设备。
USB层次连接
塔顶作为USB主控制器和跟集线器,下面接USB集线器,USB集线器将一个USB口扩展为多个USB口,多个USB口又可以通过集线器扩展出更多的接口,但USB协议中对集线器的层数是有限制的,USB1.1规定最多4层,USB2.0规定最多6层,理论上一个USB主控制器最多接127个设备,这是因为协议规定每个USB设备具有一个7bit的地址(取值范围0-127)。
一个完美的数据传输过程如下:首先由USB主控制器发出命令和数据,通过跟集线器,再通过下面的集线器发给USB设备,设备对接收到的数据极性处理后,返回一些信息或者数据,它首先到达上一层的集线器,上层的集线器再交给更上层的集线器,一直到USB的主控制器,最终USB主控制器讲述CPU处理。
文中我们对USB的拓扑结构和数据通信过程做了一个详细的描述,你学到了吗?