OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 169918|回复: 413

初学者即将毕业实习,希望利用最后两个月的学习顺利找到工作,占个地方做些笔记O(∩_∩)O哈哈~

    [复制链接]

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
发表于 2015-11-3 15:49:17 | 显示全部楼层 |阅读模式
本帖最后由 229382777@qq.com 于 2017-2-28 10:58 编辑

        买了原子的mini开发板快一年了,一直都是断断续续的学,学习进度经常被打断,导致现在连前面的知识也忘的差不多了,自身学习的条件可能没有大学生好,即将面临毕业实习,希望在最后这两个月能把mini教程全部过完并掌握,争取出去后能适应的了这方面的工作。在此借原子哥的宝地一用,也方便自己日后的复习,学习过程中可能会出现挺多错误的地方,也希望大家能帮忙指出其中错误的地方。为了保证自己的身体健康,不忙的情况下一般都会晚上9点前进行更新,每天会坚持去操场跑步。希望各位也注重身体健康,身体是革命的本钱O(∩_∩)O!

目录

所在页数 所在楼层楼层主题
1
1
1
1
1
1
1
1
2
2
2
2
2
2
2
2
3
3
3
3
3
3
3
3
4
4
4
4
4
4
4
4
4
4
4
4
2#  
20#
25#
32#
38#
41#
44#
46#
54#
59#
60#
63#
71#
78#
85#
86#
100#
106#
110#
117#
122#
134#
144#
148#
152#
154#
155#
158#
163#
164#
167#
171#
173#
176#
185#
189#之后
硬件篇
SYSTEM文件部分(一)
SYSTEM文件部分(二)
LED灯
按键
串口
外部中断
独立、窗口看门狗
定时器中断
PWM互补输出及死区时间
输入捕获
OLED
LCD
LCD(二)
RTC
待机唤醒
ADC
内部温度传感器
DAC
DMA
IIC
SPI
触摸屏
FLASH模拟EEPROM
内存管理
SD卡
FATFS
汉字显示实验
图片显示
IAP
触控USB鼠标实验
M3内核基础知识
UCOS任务调度
UCOS信号量和邮箱
消息队列、信号量、软件定时器
知识点分享
我的博客:http://blog.csdn.net/itdo_just
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 15:58:29 | 显示全部楼层

按照原子哥的mini教程顺序的学一遍,先是简单的硬件部分

硬件篇
(以下部分由自己通过网络查找资料和自己总结的一些内容)

硬件篇的内容寄存器版本和库函数的版本没有什么差别



USB电源+5V通过AMS1117-3.3的稳压芯片降压到3.3V供给MCU,回路中有500mA的自恢复保险丝进行保护

通过 F1(自恢复保险丝)接到右侧,在正常工作的情况下,保险丝可以直接看成导线,自恢复保险丝的作用是,当你后级电路哪个地方有发生短路的时候,保险丝会自动切断电路,保护开发板以及电脑的 USB 口,当电路正常后,保险丝会恢复畅通,正常工作。

AMS1117-3.3V:AMS1117的片上微调把基准电压调整到1.5%的误差以内,而且电流限制也得到了调整,以尽量减少因稳压器和电源电路超载而造成的压力。


电容:可以看到稳压芯片的两边各有一个0.1uF220uF的电容

首先了解电容前先说一下电容的必要性

电磁干扰EMI去耦电容的应用。首先要介绍一下去耦电容的应用背景,这个背景就是电磁干扰,也就是“传说中”的EMI。举几个例子:冬天比较干燥的时候手触碰到电脑外壳、铁柜子等物品的时候会被点击,这就是“静电放电”现象,也称为ESD。老式的收音机和电视会出现杂音,这就是“快速瞬间群脉冲”的效果,也称之为 EFT。以前的老电脑,有的性能不是很好,带电热插拔优盘、移动硬盘等外围设备的时候,内部会产生一个百万分之一秒的电源切换,直接导致电脑出现蓝屏或者重启现象,就是热插拔的“浪涌”效果,称之为 Surge。电磁干扰的内容有很多,有些内容非常重要。这些问题大家不要认为是小问题,比如一个简单的静电放电,我们用手能感觉到的静电,可能已经达到 3KV 以上了,如果用眼睛能看得到的,至少是 5KV 了,只是因为这个电压虽然很高,能量却非常小,持续的时间非常短,因此不会对人体造成伤害。但是应用的这些半导体元器件就不一样了,一旦瞬间电压过高,就有可能造成器件的损坏。而且,即使不损坏也已经严重干扰到设备的正常使用了。基于以上的这些问题,就诞生了电磁兼容(EMC)这个名词。

 

去耦电容:

回到那幅图的两个电容上面,容值比较大的电容220uF那个,理论上可以理解成水缸或者水池子,同时,可以直接把电流理解成水流。

作用一:缓冲作用。当上电的瞬间,电流从电源处流下来的时候,不稳定,容易冲击电子器件,加个电容可以起到缓冲作用。就如同我们直接用水龙头的水浇地,容易冲坏花花草草。我们只需要在水龙头处加个水池,让水经过水池后再缓慢流进草地,就不会冲坏花草,起到有效的保护作用。

作用二:稳定作用。后级电子器件的功率大小都不一样,而器件正常工作的时候,所需电流的大小也不是一成不变的。比如后级有个器件还没有工作的时候,电流消耗是 100mA,突然它参与工作了,电流猛的增大到了 150mA,这个时候如果没有一个水缸的话,电路中的电压(水位)就会直接突然下降。而电路中有些电子元器件,必须高于一定的电压才能正常工作,电压太低就直接不工作了,这个时候水缸就必不可少了。电容会在这个时候把存储在里边的电量释放一下,稳定电压。电容的选取,第一个参数是耐压值的考虑。USB的5V 系统,电容的耐压值要高于5V,一般推荐 1.5 倍到 2 倍即可。第二个参数是电容容值,这个就需要根据经验来选取了,选取的时候,要看这个电容起作用的整套系统的功率消耗情况,如果系统耗电较大,波动可能比较大,那么容值就要选大一些,反之可以小一些。比如上边讲电容作用二的时候,电流从 100mA 突然增大到 150mA 的时候,其实即使加上这个电容,电压也会轻微波动,比如从 3.3V 波动到 3.2V,但是只要板子上的器件在电压 3.2V 以上也可以正常工作的话,这点波动是被容许的,但是如果不加或者加的很小,电压波动比较大,有些器件的工作就会不正常了。但是如果加的太大,占空间并且价格也高,所以这个地方电容的选取多参考经验。

 

再看一下那个比较小的电容(104):它容值较小,是 0.1uF,也就是 100nF,是用来滤除高频信号干扰的。比如 ESD, EFT 等。初中学过电容的特性——可以通交流隔直流,但是电容的参数对不同频率段的干扰的作用是不一样的。这个 100nF 的电容,是前辈根据干扰的频率段,根据板子的参数,根据电容本身的参数所总结出来的一个值。也就是说,以后在设计数字电路的时候,在电源处的去耦高频电容,直接用这个 0.1uF 就可以了。还有在所有的 IC 器件的 VCC 和 GND 之间,都会放一个 0.1uF 的高频去耦电容,特别在布板的时候,这个 0.1uF 电容要尽可能的靠近 IC,尽量很顺利的与这个 IC 的 VCC 和 GND 连到一起。

 

SMBJ5.0A和SMBJ3.3A:SMBJ表示TVS管的系列

TVS或称瞬变电压抑制二极管是在稳压管工艺基础上发展起来的一种新产品,其电路符号和普通稳压二极管相同,外形也与普通二极管无异,当TVS管两端经受瞬间的高能量冲击时,它能以极高的速度(最高达1*10-12秒)使其阻抗骤然降低,同时吸收一个大电流,将其两端间的电压箝位在一个预定的数值上,从而确保后面的电路元件免受瞬态高能量的冲击而损坏。属于防过电压、过电流的保护元件。


晶振:

晶振通常分为无源晶振和有源晶振两种类型,无源晶振一般称之为 crystal(晶体),而有源晶振则叫做 oscillator(振荡器)。有源晶振是一个完整的谐振振荡器,它是利用石英晶体的压电效应来起振,所以有源晶
振需要供电,当我们把有源晶振电路做好后,不需要外接其它器件,只要给它供电,它就可以主动产生振荡频率,并且可以提供高精度的频率基准,信号质量也比无源信号要好。无源晶振自身无法振荡起来,它需要芯片内部的振荡电路一起工作才能振荡,它允许不同的电压,但是信号质量和精度较有源晶振差一些。相对价格来说,无源晶振要比有源晶振价格便宜很多。无源晶振两侧通常都会有个电容,一般其容值都选在 10pF~40pF 之间,如果手册中有具体电容大小的要求则要根据要求来选电容,原子哥使用的是 22pF 就是比较好的选择,这是一个长久以来的经验值,具有极其普遍的适用性。

 

上下拉电阻:

上拉电阻就是将不确定的信号通过一个电阻拉到高电平,同时此电阻也起到一个限流作用,下拉就是下拉到低电平。比如我们的 IO 设置为开漏输出高电平或者是高阻态时,默认的电平就是不确定的,外部经一个电阻接到 VCC,也就是上拉电阻,那么相应的引脚就是高电平;经一个电阻到 GND,也就是下拉电阻,那么相应的引脚就是一个低电平。上拉电阻应用很多,都可以起到什么作用呢?现在主要先了解最常用的以下 4 点。
1、 OC 门要输出高电平,必须外部加上拉电阻才能正常使用,其实 OC 门就相当于单片机 IO 的开漏输出。

2、加大普通 IO 口的驱动能力。单片机内部 IO 口的上拉电阻,一般都是在几十K 欧,因此外部加个上拉电阻,可以形成和内部上拉电阻的并联结构,增大高电平时电流的输出能力。

3、在电平转换电路中,比如和三极管配合使用,可以利用三极管开关作用3.3V去控制12V电压,这里的上拉电阻其实起到的是限流电阻的作用.

4、单片机中未使用的引脚,比如总线引脚,引脚悬空时,容易受到电磁干扰而处于紊乱状态,虽然不会对程序造成什么影响,但通常会增加单片机的功耗,加上一个对 VCC 的上拉电阻或者一个对 GND 的下拉电阻后,可以有效的抵抗电磁干扰。那么在进行电路设计的时候,又该如何选择合适的上下拉电阻的阻值呢?
1、从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小。
2、从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大。

3、在开漏输出时,过大的上拉电阻会导致信号上升沿变缓。我们来解释一下:实际电平的变化都是需要时间的,虽然很小,但永远都达不到零,而开漏输出时上拉电阻的大小就直接影响了这个上升过程所需要的时间。想一下,如果电阻很大,而信号频率又很快的话,最终将导致信号还没等上升到高电平就又变为低了,于是信号就无法正确传送了。

综合考虑各种情况,我们常用的上下拉电阻值大多选取在 1K 到 10K 之间,具体到底多大通常要根据实际需求来选,通常情况下在标准范围内就可以了,不一定是一个固定的值。

 

三极管(Q1为NPN,Q2为PNP):只考虑上面原理图的三极管用法(开关特性)

三极管的用法特点,关键点在于 b 极(基极)和 e 级(发射极)之间的电压情况,对于PNP 而言, e 极电压只要高于 b 级 0.7V(硅管) 以上,这个三极管 e 级和 c 级之间就可以顺利导通。也就是说,控制端在 b 和 e 之间,被控制端是 e 和 c 之间。同理, NPN 型三极管的导通电压是 b 极比 e 极高 0.7V(硅管),总之是箭头的始端比末端高 0.7V(硅管) 就可以导通三极管的 e 极和 c 极。
我的博客:http://blog.csdn.net/itdo_just
回复 支持 6 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-9 17:50:12 | 显示全部楼层

实战:串口

 

USART介绍

通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。 USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。使用多缓冲器配置的DMA方式,可以实现高速数据通信。

 

任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。

RX:接收数据串行输入。通过采样技术来区别数据和噪音,从而恢复数据。
TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。

 

偶校验:校验位使得一帧中的7或8个LSB数据以及校验位中’1’的个数为偶数。
例如:数据=00110101,有4个’1’,如果选择偶校验(在USART_CR1中的PS=0),校验位将是’0’。
奇校验:此校验位使得一帧中的7或8个LSB数据以及校验位中’1’的个数为奇数。
例如:数据=00110101,有4个’1’,如果选择奇校验(在USART_CR1中的PS=1),校验位将是’1’。
传输模式:如果USART_CR1的PCE位被置位,写进数据寄存器的数据的MSB位被校验位替换后发送出去(如果选择偶校验偶数个’1’,如果选择奇校验奇数个’1’)。如果奇偶校验失败,USART_SR寄存器中的PE标志被置’1’,并且如果USART_CR1寄存器的PEIE在被预先设置的话,中断产生。

 

串口各种模式的设置

同步模式:通过在USART_CR2寄存器上写CLKEN位选择同步模式。

LIN(局域互联网)模式:是通过设置USART_CR2寄存器的LINEN位选择。

单线半双工模式:通过设置USART_CR3寄存器的HDSEL位选择。

智能卡模式:设置USART_CR3寄存器的SCEN位选择智能卡模式。

IrDA模式:通过设置USART_CR3寄存器的IREN位选择IrDA模式。

串口DMA发送接收:USART可以利用DMA连续通信。 Rx缓冲器和Tx缓冲器的DMA请求是分别产生的。使用DMA进行发送,可以通过设置USART_CR3寄存器上的DMAT位激活。可以通过设置USART_CR3寄存器的DMAR位激活使用DMA进行接收

 

串口设置

1,串口时钟使能:APB1ENR或APB2ENR

2,串口复位:APB1RSTR或APB2RSTR

3,串口波特率设置:USART_BRR(通过计算,上面笔记有)

4,串口控制:USART_CR1~3,中文参考手册542~546页,内容比较多

5,数据发送与接收:USART_DR

6,串口状态:USART_SR

 

USART_SR 状态寄存器

比较重要的两个位

RXNE(读数据寄存器非空),当该位被置 1 的时候,就是提示已经有数据被接收到了,并
且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以将
该位清零,也可以向该位写 0,直接清除。
TC(发送完成),当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如
果设置了这个位的中断,则会产生中断。该位也有两种清零方式: 1)读 USART_SR,写
USART_DR。 2)直接向该位写 0。

 

USART_DR寄存器

DR[8: 0]为串口数据,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给
发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能。 TDR 寄存器提供了内部总
线和输出移位寄存器之间的并行接口。 RDR 寄存器提供了输入移位寄存器和内部总线之间的并
行接口。如下图



半主机模式:
半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。例如,使用此机制可以启用 C 库中的函数,如 printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和键盘。

这种机制很有用,因为开发时使用的硬件通常没有最终系统的所有输入和输出设备。 半主机可让主机来提供这些设备。半主机是通过一组定义好的软件指令(如 SVC)来实现的,这些指令通过程序控制生成异常。 应用程序调用相应的半主机调用,然后调试代理处理该异常。 调试代理提供与主机之间的必需通信。
简单的来说,半主机模式就是通过仿真器实现开发板在电脑上的输入和输出。机制的运行需要仿真器,否则无法运行, 开发式一般单片机需要独立运行,开发者应去掉仿真器,把printf函数通过单片机的外设来实现,例如通过开发板的串口,lcd或者sd卡。

以下是串口初始化代码
[mw_shl_code=c,true]void uart_init(u32 pclk2,u32 bound) { float temp; u16 mantissa; u16 fraction; temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV,前面的笔记已经解释,这里就不重复了 mantissa=temp; //得到整数部分 fraction=(temp-mantissa)*16; //得到小数部分 mantissa<<=4; mantissa+=fraction; RCC->APB2ENR|=1<<2; //使能PORTA口时钟 RCC->APB2ENR|=1<<14; //使能串口时钟 GPIOA->CRH&=0XFFFFF00F;//IO状态设置 GPIOA->CRH|=0X000008B0;//IO状态设置 RCC->APB2RSTR|=1<<14; //复位串口1 RCC->APB2RSTR&=~(1<<14);//停止复位 //波特率设置 USART1->BRR=mantissa; // 波特率设置 USART1->CR1|=0X200C; //1位停止,无校验位(原子哥这里应该备注错误了,停止位是通过CR2的12和13位来设置的,CR1设置不了,这里应该备注为USART使能,发送接收使能) #if EN_USART1_RX //如果使能了接收 //使能接收中断 USART1->CR1|=1<<8; //PE中断使能 USART1->CR1|=1<<5; //接收缓冲区非空中断使能 MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级 #endif } [/mw_shl_code]

上面初始化过程也发现原子哥有个备注写错了,USART1->CR1|=0X200C;备注的是1位停止位,停止位是通过CR21213位来设置的,CR1设置不了,这里应该备注为USART使能,发送接收使能。

 

关于原子哥的中断处理函数也写的非常好,自己也用之前学过的位字段的方式改了一下代码,原理都差不多,原子哥用16位的变量当做寄存器操作,0~13位用于保存字节数,1415位用于判断换行符来标示结束,用位字段主要考虑到程序的可读性,因为原子哥的标志位没有明确指令其作用,而用位字段能清晰的描述,当然,考虑到程序的效率,当然还是原子哥的方法好。以下附上我修改过的代码
[mw_shl_code=c,true]typedef struct{ unsigned REC_Num : 14; //前0~13位保存接收到的字节数 unsigned REC_0X0D : 1; //收到0x0d表示换行 unsigned REC_END : 1; //结束一次接收 }usart_rx_sta; usart_rx_sta USART_RX_STA; //接收状态标记 void USART1_IRQHandler(void) { u8 res; if(USART1->SR&(1<<5))//接收到数据 { res=USART1->DR; if(USART_RX_STA.REC_END == 0)//接收未完成 { if(USART_RX_STA.REC_0X0D == 1)//接收到了0x0d { if(res!=0x0a) memset(&USART_RX_STA, 0 ,sizeof(USART_RX_STA));//接收错误,重新开始 else USART_RX_STA.REC_END = 1; //接收完成了 }else //还没收到0X0D { if(res==0x0d)USART_RX_STA.REC_0X0D = 1; else { USART_RX_BUF[USART_RX_STA.REC_Num]=res; USART_RX_STA.REC_Num++; if(USART_RX_STA.REC_Num>(USART_REC_LEN-1)) memset(&USART_RX_STA, 0 ,sizeof(USART_RX_STA));//接收数据错误,重新开始接收 } } } } } [/mw_shl_code]

程序的运行结果都是一样的,基于考虑实际项目,串口需要有个超时机制,以下附上自己常用的超时机制的代码,如果超过50ms都还没接收到数据则强制接收结束。以下是代码

[mw_shl_code=c,true]/* * 函数名:UartRxMonitor * 描 述:UART通信接收的监控函数,用于判定一帧数据的接收完成 * 输 入:ms - 本函数调用的间隔时间,单位ms,直接开个定时器,每毫秒中断一次,形参设为1即可 * 输 出:无 */ void UartRxMonitor(u8 ms) { u8 cnt; static u8 cntbkp=0; static u8 idletmr=0; static u8 sigpost=0; cnt = usart3_get_long; //帧的长度 if (cnt == 0) { //没接收到数据 cntbkp = 0; //当前长度清零 return; } if (cnt != cntbkp) { //接收到了新数据,累加的时间清零,如果前后长度相等则开始ms的累加 idletmr = 0; //时间累计清零 sigpost = 0; //超时标志清零 cntbkp = cnt; //记下当前长度 return; } if (sigpost != 0) { //保证接收完成标志只推送一次 return; } idletmr += ms; //毫秒数累计,50ms     if (idletmr >= FRAME_SPACE_TIME) { //如果超过50ms长度依旧相等,则说明超时了,FRAME_SPACE_TIME是个宏定义,等于50 sigpost = 1; //超时标志 //可以强制接收完成 //也可以丢弃接收缓冲区的数据,看自己需求 } }[/mw_shl_code]



我的博客:http://blog.csdn.net/itdo_just
回复 支持 4 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-6 16:00:08 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2016-1-6 16:05 编辑

感觉坛友这两篇关于汇编的文档写的不错,分享给大家,建议看一下
原网址在:http://www.openedv.com/forum.php?mod=viewthread&tid=45361

Cortex-M3汇编实践.pdf

1.08 MB, 下载次数: 6101

浅谈STM32汇编(Wizard).pdf

5.24 MB, 下载次数: 10244

我的博客:http://blog.csdn.net/itdo_just
回复 支持 4 反对 0

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-11 17:13:14 | 显示全部楼层
顶一下,支持++
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 4 反对 0

使用道具 举报

71

主题

467

帖子

0

精华

高级会员

Rank: 4

积分
800
金钱
800
注册时间
2011-11-18
在线时间
5 小时
发表于 2015-11-11 07:49:24 | 显示全部楼层
好认真呀!中国还有你这样的技校生,比本科生都强,读懂人家代码后多动手写,要不很快忘掉的
我的工作就是天天在玩
回复 支持 1 反对 1

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-6 11:55:23 | 显示全部楼层
最新更新部份,由于时间原因要出去游玩两天,所以更新比较急,发现有些地方没备注好,附件都得等回来补
我的博客:http://blog.csdn.net/itdo_just
回复 支持 2 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-16 14:41:57 | 显示全部楼层

实战:OLED

 

简介:

OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示( OrganicElectroluminesence Display,OELD)。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED效果要来得好一些。以目前的技术,OLED的尺寸还难以大型化,但是分辨率确可以做到很高。


控制器SSD1306通信接口




8080写时序以及对应程序



[mw_shl_code=c,true]对应程序: //向SSD1306写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { OLED_RS=cmd; //数据/命令标志 OLED_CS=0; OLED_WR=0; DATAOUT(dat); OLED_WR=1; OLED_CS=1; OLED_RS=1; } [/mw_shl_code]
4线SPI接口写时序及对应程序


[mw_shl_code=c,true]SPI方式对应程序: //向SSD1306写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; OLED_RS=cmd; //写命令 OLED_CS=0; for(i=0;i<8;i++) { OLED_SCLK=0; if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0; OLED_SCLK=1; dat<<=1; } OLED_CS=1; OLED_RS=1; } [/mw_shl_code]
SSD1306指令集(写入即可获得相关屏幕设置)


控制8080接口读写命令数据



假读命令(Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数据之前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二个开始,才是我们真正要读的数据。

读显存时序图:



ALIENTEK OLED 模块的 8080 接口方式需要如下一些信号线:



SSD1306初始化框图



显存与屏幕对应关系表:



一直在思考原子哥关于数组的定义,u8 OLED_GRAM[128][8] ,表示128行,8(18bit),而我们第一眼看屏幕的时候明明是64行,128列,为什么数组不定义成OLED_GRAM[8][128]呢,后面看了上面那副图就明白了,如果定义成[8][128]那么每次加1都是横向加的,而如果定义成[128][8]每次加1就是换一页,符合上图的从上往下扫。

 

程序为什么要内建GRAM?原因如下:

SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 128*64 的点阵大小。因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,那么,每次要写 8 个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位都搞清楚当前的状态( 0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进 GRAM,这样就不会影响到之前的状况了。但是这样需要能读 GRAM,对于 3 线或 4 线 SPI 模式,模块是不支持读的,而且读->改->写的方式速度也比较慢。所以我们采用的办法是在 STM32 的内部建立一个 OLED 的 GRAM(共 128*8 个字节),在每次修改的时候,只是修改 STM32 上的 GRAM(实际上就是 SRAM),在修改完了之后,一次性把 STM32 上的 GRAM 写入到 OLED 的 GRAM。当然这个方法也有坏处,就是对于那些 SRAM很小的单片机(比如 51 系列)就比较麻烦了。

函数:发现写好时序并设置好一些启动指令实现和屏的通信后,最主要的就是画点函数,几乎所有的其他函数都是基于画点函数来显示的,发现原子哥写的这些函数思维很严谨,考虑的也很充足,是自己以后需要学习的,自己没有OLED屏,学LCD的时候打算先不看原子哥的代码写一个画点的函数试试。
[mw_shl_code=c,true]//画点 //x:0~127 //y:0~63 //t:1 填充 0,清空 void OLED_DrawPoint(u8 x,u8 y,u8 t) { u8 pos,bx,temp=0; if(x>127||y>63)return;//超出范围了. pos=7-y/8; //计算得出在第几页,page0~7 bx=y%8; //得出在page里面的具体位置 temp=1<<(7-bx); //由于x,y是以左下角为准,这里要转为以page的左上角为准 if(t)OLED_GRAM[x][pos]|=temp; //相应位置1 else OLED_GRAM[x][pos]&=~temp;//相应位置0 } //在指定位置显示一个字符,包括部分字符 //x:0~127 //y:0~63 //mode:0,反白显示;1,正常显示 //size:选择字体 16/12 void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode) { u8 temp,t,t1; u8 y0=y; u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数 chr=chr-' '; //得到偏移后的值 for(t=0;t<csize;t++) //写入多个字节 { if(size==12)temp=asc2_1206[chr][t]; //调用1206字体 else if(size==16)temp=asc2_1608[chr][t]; //调用1608字体 else if(size==24)temp=asc2_2412[chr][t]; //调用2412字体 else return; //没有的字库 for(t1=0;t1<8;t1++) //写入一个字节 { if(temp&0x80)OLED_DrawPoint(x,y,mode); else OLED_DrawPoint(x,y,!mode); temp<<=1; y++; //一个bit一个bit写进去 if((y-y0)==size) //写完一个字符一列的所有bit { y=y0; //下一列,在同一行 x++; //写完一个字的一列了,退出写下一列 break; } } } } //显示字符串 //x,y:起点坐标 //size:字体大小 //*p:字符串起始地址 void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size) { while((*p<='~')&&(*p>=' ')) //判断是不是非法字符! { if(x>(128-(size/2))){x=0;y+=size;} //宽度超过屏幕的一行,显示到下一行 if(y>(64-size)){y=x=0;OLED_Clear();} //长度到屏幕底部了回到起始,清屏 OLED_ShowChar(x,y,*p,size,1); x+=size/2; p++; } }[/mw_shl_code]
我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-31 14:29:18 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2016-1-31 14:45 编辑

今天没事移植了下UCGUI,不带系统的移植,比较简单,需要的可以看下以下四个链接,有一个是原子哥提供的GUI源码,另外三个是关于移植的,关于API的应用板子暂时不在身边这试不了,带系统的移植有时间再玩一下
http://www.openedv.com/posts/list/522.htm
http://www.openedv.com/thread-20610-1-1.html
http://blog.chinaunix.net/uid-361890-id-2981509.html
http://wenku.baidu.com/link?url= ... jJQYApkkn8aEhPBq-aG


我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-28 11:58:00 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2015-12-28 12:02 编辑

实战:UCOS任务调度


1.png

任务优先级
这个概念比较好理解, ucos 中,每个任务都有唯一的一个优先级。优先级是任务的唯一标识。在 UCOSII 中,使用CPU 的时候,优先级高(数值小)的任务比优先级低的任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得 CPU 使用权,只有高优先级的任务让出 CPU 使用权(比如延时)时,低优先级的任务才能获得 CPU 使用权。UCOSII 不支持多个任务优先级相同,也就是每个任务的优先级必须不一样。
任务堆栈
就是存储器中的连续存储空间。为了满足任务切换和响应中断时保存 CPU 寄存器中的内容以及任务调用其他函数时的需要,每个任务都有自己的堆栈。在创建任务的时候,任务堆栈是任务创建的一个重要入口参数。
2.png


任务控制块OS_TCB
用来记录任务堆栈指针,任务当前状态以及任务优先级等任务属性。UCOSII 的任何任务都是通过任务控制块( TCB)的东西来控制的,一旦任务创建了,任务控制块 OS_TCB 就会被赋值。
每个任务管理块有 3 个最重要的参数: 1,任务函数指针; 2,任务堆栈指针; 3,任务优先级;任务控制块就是任务在系统里面的身份证( UCOSII 通过优先级识别任务)。
3.png

任务就绪表
简而言之就是用来记录系统中所有处于就绪状态的任务。它是一个位图,系统中每个任务都在这个位图中占据一个进制位,该位置的状态( 1 或者 0)就表示任务是否处于就绪状态。
4.png

任务调度
任务调度的作用一是在任务就绪表中查找优先级最高的就绪任务,二是实现任务的切换。
比如说,当一个任务释放 cpu 控制权后,进行一次任务调度,这个时候任务调度器首先要去任
务就绪表查询优先级最高的就绪任务,查到之后,进行一次任务切换,转而去执行下一个任务。

5.png
6.png

UCOS的五种状态
7.png

建立任务函数
OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTUprio)
该函数包括4 个参数:task:是指向任务代码的指针; pdata:是任务开始执行时,传
递给任务的参数的指针; ptos:是分配给任务的堆栈的栈顶指针; prio 是分配给任务的优
先级。

任务删除函数
INT8UOSTaskDel(INT8U prio)
其中参数prio 就是我们要删除的任务的优先级,可见该函数是通过任务优先级来实现
任务删除的。

请求任务删除函数
INT8UOSTaskDelReq(INT8U prio)

改变任务的优先级函数
INT8UOSTaskChangePrio(INT8U oldprio,INT8U newprio)

任务挂起函数
INT8UOSTaskSuspend(INT8U prio)

任务恢复函数
NT8UOSTaskResume(INT8U prio)

STM32上面运行UCOSII的步骤:
1 移植 UCOSII
2 编写任务函数并设置其堆栈大小和优先级等参数。
3 初始化 UCOSII,并在 UCOSII 中创建任务
4 启动 UCOSII
8.png

[mw_shl_code=applescript,true]//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{                                   
          OSIntEnter();                //进入中断,用来记录中断嵌套层数
    OSTimeTick();   //调用ucos的时钟服务程序,系统时钟节拍服务函数,在每个时钟节拍了解每个任务的延时状态,使已经到达延时时限的非挂起任务进入就绪状态;               
OSIntExit();    //触发任务切换软中断,退出中断服务函数,该函数可能触发一次任务切换

}
[/mw_shl_code]
9.png
10.png
11.png


最后看下PendSV(可悬起系统调用)的知识,这个中断在任务调度的时候起着非常重要的作用
12.png
13.png
14.png
15.png


这些都是ucos的基本知识,原子哥的教程讲的不是很详细,但是提供了很多非常好的资料,“ucOS-II入门教程(任哲)”这个PPT讲的非常好,简单描述了UCOS的原理,我看了2遍,还有一份资料也很好,叫“ucosiiSTM32上的移植详解”这个我也看了三遍,对ucos有了个大概的理解,以下附上资料,原子哥的光盘中也有


ucosii在STM32上的移植详解.pdf

207.97 KB, 下载次数: 2133

我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-27 18:55:33 | 显示全部楼层

实战:DMA

 

简介:

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
STM32 最多有2个DMA控制器(DMA2仅存在大容量产品中), DMA1有7个通道。DMA2有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个DMA请求的优先权。

 

DMA主要特性
● 12个独立的可配置的通道(请求): DMA1有7个通道, DMA2有5个通道
● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
● 支持循环的缓冲器管理
● 每个通道都有3个事件标志(DMA半传输、 DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
● 存储器和存储器间的传输
● 外设和存储器、存储器和外设之间的传输
● 闪存、 SRAM、外设的SRAM、 APB1、 APB2和AHB外设均可作为访问的源和目标。
● 可编程的数据传输数目:最大为65535
下面为功能框图:


本次实验用到的ADC1是通道1



自己将原子哥的例程实验后,将DMA和ADC、DAC进行了结合,原子哥的例程是通过DMA读取数据到串口发送到电脑显示,并且通过寄存器的递减来显示传递的进度然后在电脑显示,实现这个功能后,自己又对前面两章学习的内容进行了结合,设置4个ADC通道,2路DAC输出,DMA进行数据传递,并且给LCD增加了新的功能,下面介绍DMA需要配置的几个寄存器


DMA中断状态寄存器(DMA_ISR)

这次自己分别试了下使用中断和不使用中断,都没有问题,下面说下不使用中断的配置

DMA_GetFlagStatus(DMA1_FLAG_TC1)!=RESET //等待通道1传输完成




DMA中断标志清除寄存器(DMA_IFCR)

DMA_ClearFlag(DMA1_FLAG_TC1);//清除通道1传输完成标志


DMA通道x配置寄存器(DMA_CCRx)(x = 17)

DMA_InitStruct.DMA_BufferSize = cndtr;           //设置为4,有4个通道的ADC数据

DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;//外设通过DMA向内存存入数据

DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;  //DMA通道x不设置为内存到内存传输

DMA_InitStruct.DMA_MemoryBaseAddr = cmar;    //DMA内存基地址

DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;    //内存地址寄存器递增

DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;            //循环DMA模式(适合ADC扫描类)

DMA_InitStruct.DMA_PeripheralBaseAddr = cpar;              //DMA外设ADC基地址

DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变

DMA_InitStruct.DMA_Priority=DMA_Priority_High;     //DMA通道优先级高

DMA_Init(DMA_CHx, &DMA_InitStruct);  

DMA_Cmd(DMA1_Channel1,ENABLE);          //使能DMA1通道1传输



DMA通道x外设地址寄存器(DMA_CPARx)(x = 17)

DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据位宽16位(AD转换数据为16位)


DMA通道x存储器地址寄存器(DMA_CMARx)(x = 17)

DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据位宽16位



设置扫描模式,不用每次都用单次转换和读取一个规则通道的值了,那样的话必须按顺序来读,而用ADC的扫描模式则可以设置ADC的采样顺序了.

ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//开启连续转换

ADC_InitStruct.ADC_ScanConvMode = ENABLE;//开启扫描模式

ADC_InitStruct.ADC_NbrOfChannel = 4;//顺序进行规则转换的ADC通道的数目

//设置4通道的采样顺序和采样时间(可以更改)

ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);

ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_239Cycles5);

ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_239Cycles5);

ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_239Cycles5);



最后设置一个数组来保存ADC_DR寄存器的4个通道值

ADC_ConvertedValue[4];

MY_DMA_Init(DMA1_Channel1,  (u32)&ADC1->DR,  (u32)ADC_ConvertedValue,  4);//传递进形参地址

 

关于LCD的新功能就是将printf函数重映像到屏幕上,之前试过没有x,y轴定位的方式,但是实际意义不大,这次修改了一下可以支持定位操作了,实现的方法来自于论坛的网友提供,对于原子哥之前要取出整数和小数部分显得比较麻烦,所以用printf%f直接输出就可以了,以后直接可以用屏幕来打印信息了,免得开串口那么麻烦,哈哈,懒人神技!
printf重定向函数:

[mw_shl_code=c,true]//重定义fputc函数 int fputc(int ch, FILE *f) { static u16 x=0, y=0; if(ch == '\n') { x = 0; y += 12; return ch; } if(x > 240-6) { x = 0; y += 12; } if(y > 320-12) { y = 0; LCD_Clear(WHITE); } LCD_Show_Char(x, y, ch, 12); x+=6; return ch; } #include <stdarg.h>//加入头文件[/mw_shl_code] [mw_shl_code=c,true]void LCD_ShowString(u16 x,u16 y,const u8 *p) { u16 x1; x1=x; while(*p!='\0') { if(x>=lcddev.width){x=x1;} if(y>=lcddev.height){y=x=0;LCD_Clear(WHITE);} if(*p=='\n') { y+=16; x=x1; } else { LCD_Show_Char(x,y,*p,16); x+=8; } p++; } } void LCD_printf(u16 x,u16 y,const char *format, ...) { char tmp[25]; __va_list arg; va_start(arg, format); vsprintf(tmp,format,arg); va_end(arg); LCD_ShowString(x,y,(const u8*)tmp); }[/mw_shl_code]


DAC输出:

Dac1_Set_Vol(1500);  //1.5V

Dac2_Set_Vol(2100);  //2.1V        

ADC获取:

adcx = ADC_ConvertedValue;

temp = (float)adcx*(3.3/4096);

LCD_printf(96, 40+i*16, "%1.3fV", temp);

算出值后直接定位打印出来就好了,非常方便,下面是例程的效果图,4路ADC+2路DAC+DMA的效果(本章代码在附件中,有需要的可以参考下)

ADC+DAC+DMA.zip

5.52 MB, 下载次数: 2530

我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

38

主题

2061

帖子

6

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3273
金钱
3273
注册时间
2012-1-16
在线时间
37 小时
发表于 2015-11-3 16:33:48 | 显示全部楼层
如果用这个态度去学习,成为大神级人物指日可待,加油!
站在巨人的肩膀上不断的前进。。。
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-3 16:40:18 | 显示全部楼层
已收藏,加油~~~~~

帖子从排版、内容质量都很棒,坚持下去必有很大提升~~~~~

楼主作息很规律啊,我都计划好几个月作息依然艰难于执行,向你学习~~~~~
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 17:05:16 | 显示全部楼层
回复【3楼】xiaoyan:
---------------------------------
谢谢,前期好学一点,后面难度就很大了
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 17:05:47 | 显示全部楼层
回复【4楼】龙之谷:
---------------------------------
希望能有跟你一样的毅力
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

3

主题

10

帖子

0

精华

初级会员

Rank: 2

积分
94
金钱
94
注册时间
2015-11-3
在线时间
29 小时
发表于 2015-11-3 17:16:51 | 显示全部楼层
火前留名,我会跟下去的哈哈~~~~~~~~~~~~~
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 17:23:20 | 显示全部楼层
回复【7楼】如画_M10:
---------------------------------
哈哈,一起努力
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

12

主题

336

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1571
金钱
1571
注册时间
2015-8-9
在线时间
624 小时
发表于 2015-11-3 18:41:43 | 显示全部楼层
期待楼主跟新
回复 支持 反对

使用道具 举报

14

主题

1592

帖子

0

精华

资深版主

Rank: 8Rank: 8

积分
2622
金钱
2622
注册时间
2014-7-17
在线时间
350 小时
发表于 2015-11-3 18:46:11 | 显示全部楼层
期待楼主跟新
回复 支持 反对

使用道具 举报

18

主题

238

帖子

3

精华

金牌会员

Rank: 6Rank: 6

积分
1823
金钱
1823
注册时间
2014-8-5
在线时间
211 小时
发表于 2015-11-3 20:09:27 | 显示全部楼层
谢谢楼主分享,楼主学的很细、认真
回复 支持 反对

使用道具 举报

19

主题

217

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
427
金钱
427
注册时间
2015-4-1
在线时间
34 小时
发表于 2015-11-3 20:26:20 | 显示全部楼层
学习了,很棒,总结的到位,继续努力,加油
一直在路上,一直在成长。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-3 21:10:24 | 显示全部楼层
不错啊,楼主好好坚持
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 21:11:36 | 显示全部楼层
回复【9楼】Akatsuki_lim:
---------------------------------
回复【10楼】FantaSy_:
---------------------------------
回复【11楼】513393302@qq.com:
---------------------------------
回复【12楼】济世良驹:
---------------------------------
谢谢大家支持
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 21:12:47 | 显示全部楼层
回复【13楼】正点原子:
---------------------------------
谢谢原子哥
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

22

主题

180

帖子

1

精华

高级会员

Rank: 4

积分
616
金钱
616
注册时间
2015-6-29
在线时间
101 小时
发表于 2015-11-3 21:47:30 | 显示全部楼层
好,又来一个了,顶顶
我是菜鸟
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-3 23:03:59 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2017-3-17 13:59 编辑

更新完!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-4 06:01:22 | 显示全部楼层
早上好………
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

13

主题

61

帖子

0

精华

高级会员

Rank: 4

积分
686
金钱
686
注册时间
2015-6-15
在线时间
242 小时
发表于 2015-11-4 08:01:37 | 显示全部楼层
回复【18楼】229382777@qq.com:
---------------------------------
早上好  苦逼大四学生  马上毕业 实习了  一直在自学 不说了  得去上课了 迟到两分钟了 加油
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-4 14:37:01 | 显示全部楼层

SYSTEM文件部分(一)

先让我把这个图记一下(以后提醒自己多用堆栈窗口和内存窗口调试程序)

启动代码:

原子哥教程里面看到这句话,刚好有这方面的资料,资料对startup_stm32f10x_hd.s里面的200多行的汇编代码进行了详细的讲解,附件在底下,强烈建议看一下!

时钟树:经常要去找挺麻烦的,贴上以后直接在这看了


HSE
时钟
高速外部时钟信号(HSE)由以下两种时钟源产生:
● HSE外部晶体/陶瓷谐振器
● HSE用户外部时钟
为了减少时钟输出的失真和缩短启动稳定时间,晶体/陶瓷谐振器和负载电容器必须尽可能地靠
近振荡器引脚。负载电容值必须根据所选择的振荡器来调整。

HSI时钟

HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。
HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振
荡器短。然而,即使在校准之后它的时钟频率精度仍较差。

 PLL

内部PLL可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟。
PLL的设置(选择HIS振荡器除2或HSE振荡器为PLL的输入时钟,和选择倍频因子)必须在其被激
活前完成。一旦PLL被激活,这些参数就不能被改动。
如果PLL中断在时钟中断寄存器里被允许,当PLL准备就绪时,可产生中断申请。
如果需要在应用中使用USB接口, PLL必须被设置为输出48或72MHZ时钟,用于提供48MHz的
USBCLK时钟。

LSE时钟

LSE晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时功能提供
一个低功耗且精确的时钟源。
LSE晶体通过在备份域控制寄存器(RCC_BDCR)里的LSEON位启动和关闭。
在备份域控制寄存器(RCC_BDCR)里的LSERDY指示LSE晶体振荡是否稳定。在启动阶段,直
到这个位被硬件置’1’后, LSE时钟信号才被释放出来。如果在时钟中断寄存器里被允许,可产
生中断申请。

LSI时钟

LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和
自动唤醒单元提供时钟。 LSI时钟频率大约40kHz(在30kHz和60kHz之间)。进一步信息请参考数
据手册中有关电气特性部分。
LSI RC可以通过控制/状态寄存器(RCC_CSR)里的LSION位来启动或关闭。
在控制/状态寄存器(RCC_CSR)里的LSIRDY位指示低速内部振荡器是否稳定。在启动阶段,直
到这个位被硬件设置为’1’后,此时钟才被释放。如果在时钟中断寄存器(RCC_CIR)里被允许,
将产生LSI中断申请。

时钟安全系统(CSS)

时钟安全系统可以通过软件被激活。一旦其被激活,时钟监测器将在HSE振荡器启动延迟后被
使能,并在HSE时钟关闭后关闭。
如果HSE时钟发生故障, HSE振荡器被自动关闭,时钟失效事件将被送到高级定时器(TIM1和
TIM8)的刹车输入端,并产生时钟安全中断CSSI,允许软件完成营救操作。此CSSI中断连接到
Cortex?-M3的NMI中断(不可屏蔽中断)。

系统时钟(SYSCLK)选择

系统复位后, HSI振荡器被选为系统时钟。当时钟源被直接或通过PLL间接作为系统时钟时,它
将不能被停止。
只有当目标时钟源准备就绪了(经过启动稳定阶段的延迟或PLL稳定),从一个时钟源到另一个时
钟源的切换才会发生。在被选择时钟源没有就绪时,系统时钟的切换不会发生。直至目标时钟
源就绪,才发生切换。
在时钟控制寄存器(RCC_CR)里的状态位指示哪个时钟已经准备好了,哪个时钟目前被用作系统
时钟。



Delay.c (寄存器版本和库函数版本差别不大)

SysTick定时器
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号: 15)。在以前,大多操
作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。例如,
为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期
的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时
器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问
它的寄存器,以维持操作系统“心跳”的节律。
Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的CM3芯片都带有这个定时
器,软件在不同 CM3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,
CM3上的自由运行时钟),或者是外部时钟(CM3处理器上的STCLK信号)。不过,STCLK的
具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视
芯片的器件手册来决定选择什么作为时钟源。
SysTick定时器能产生中断,CM3为它专门开出一个异常类型,并且在向量表中有它的一
席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为在所有CM3
产品间对其处理都是相同的。



延时的 ms 数不能太长。否则超出了 LOAD 的范围,高位会被舍去,导致延时不准。最大延迟 ms 数可以通过公式: nms<=0xffffff*8*1000/SYSCLK 计算。 SYSCLK单位为 Hz, nms 的单位为 ms。如果时钟为 72M,那么 nms 的最大
值为 1864ms。超过这个值,建议通过多次调用 delay_ms 实现,否则就会导致延时不准确。


后面delay_ms()和delay_us()两个函数就根据上面的fac_us和fac_ms进行乘法运算

OS自己还没接触过,不过看了原子哥的时钟摘取的方法也是脑洞大开


Sys.c
(寄存器版本和库函数版本的差别有点大,库函数只介绍了位带操作,而寄存器版本的教程信息量好大…..)

位带操作



支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM3中,

有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区

的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自
己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访

问这些字时,就可以达到访问原始比特的目的。

按上面这幅图的计算公式再来看原子哥的代码就能大概看懂什么意思了,原子哥真牛B,一直不怎么会使用#define,以后也学着这样写试试

<<CM3 权威指南>>第五章(87 页~92 页),对比了使用位带操作和不使用位带操作的区别,包括读、写、以及汇编代码的区别。

 

*((volatile unsigned long *)(addr))这个解释下面该论坛的大神已经解释的很清楚了,简单明了

http://www.openedv.com/posts/list/918.htm


“<<和>>实现乘除法的优势”:位操作只需一个指令周期即可完成,而大部分的C 编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求2n 方的余数,均可使用位操作的方法来代替。通常如果需要乘以或除以2n,都可以用移位的方法代替。如果乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,提醒自己以后尽量多用移位来代替乘除。


Stm32_Clock_Init(9):
库函数版本加了SystemInit,寄存器用的是原子哥写的一个Stm32_Clock_Init(9);函数

寄存器使用的是Stm32_Clock_Init(u8 PLL),库函数使用的SystemInit(),这两个里面的代码几乎都是一样的

1、先是复位配置向量表

2、配置中断向量表基址和偏移量

3、使能外部时钟HSE

4、倍频PLL选择为9倍频,即72Mhz

5、PLL作为系统时钟


Sys.c其中有这个函数还挺好用的,直接调用就能实现软复位

void Sys_Soft_Reset(void)

{  

       SCB->AIRCR =0X05FA0000|(u32)0x04;        

}    


低功耗一览表


进入待机模式

库函数:PWR_EnterSTANDBYMode()

寄存器:Sys_Standby() -> 这里面有一个函数是WFI_SET()用于进入模式的操作

__asm void WFI_SET(void)

{
         WFI;

}

该函数其实是在 C 语言里面嵌入一条汇编指令,因为CM3 内核的 STM32 支持的 THUMB 指令,并不能内嵌汇编,所以需要通过这个方法来实现汇编代码的嵌入。

__asm关键字:启动内联汇编并且能写在任何c/c++合法语句之处.它不能单独出现.它必须接汇编指令、一组被大括号包含的指令或一对空括号.术语“__asm 块”在这里是任意一个指令或一组指令无论是否在括号内。括号可以清晰的将C或C++代码和汇编代码分开,并且避免了不必要的重复__asm关键字。括号也能避免模糊性。

下面原子哥还有几个类似的函数,记录下方便以后调用

//关闭所有中断
__asm void INTX_DISABLE(void)
{
         CPSID I;

}
//开启所有中断
__asm void INTX_ENABLE(void)
{
         CPSIE I;

}

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
         MSR MSP, r0 //set Main Stack value
         BX r14

}


JTAG和SWD模式设置

// mode: JTAG,SWD 模式设置;00,全使能;01,使能 SWD;10,全关闭;

寄存器:void JTAG_Set(u8 mode) //用于设置 JTAG 的模式

库函数:GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

解析 STM32 的启动过程.pdf

361.04 KB, 下载次数: 5115

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-4 15:01:24 | 显示全部楼层
非常棒,nice!!!!!

有个小问题:图中LSE是外部低速时钟,标注成了32M。
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-4 15:06:56 | 显示全部楼层
回复【21楼】龙之谷:
---------------------------------
非常感谢,手误,已经改过来了
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-4 23:05:32 | 显示全部楼层
晚安
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-5 06:17:55 | 显示全部楼层
早上好………
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-5 14:42:47 | 显示全部楼层

SYSTEM文件部分(二)

中断管理函数

以下是几个概念

可屏蔽中断:

可屏蔽中断由有中断能力的外围设备所产生,包括处在定时模式的定时器溢出。每个可屏蔽中断源可以由中断使能位所单独关闭,或者由寄存器中的通用中断使能位GIE一齐关闭。


非屏蔽中断:

非屏蔽中断是出现在NMI线上的请求 ,不受中断标志位IF的影响,在当前指令执行完以后,CPU就响应。这种中断通常用于电源故障。非屏蔽中断的优先权高于屏蔽中断。


区别:

可屏蔽中断和不可屏蔽中断都属于外部中断,是由外部中断源引起的;但它们也有区别:可屏蔽中断是通过CPU的INTR引脚引入,当中断标志

IF=1时允许中断,当IF=0时禁止中断,不可屏蔽中断是由NMI引脚引入,不受IF标志的影响

 

过程:

简述:保存断点入栈,响应经中断控制器8259优先级判断后的中断请求,恢复断点。
I/O设备发出的所有中断(IRQ)都可以产生可屏蔽中断,受标志位IF的影响,根据中断循序标志的设置来判断CPU是否响应中断请求。
    1.接口发出中断请求
    2.当前指令执行完后,CPU响应中断
    3.终端类型号N送CPU
    4.当前的EFR CS HE EIP 推入栈
    5.清除IF(中断允许标志位,IF=0,禁止CPU响应外部中断请求,IF=1,允许响应)和TF(跟踪标志,也称单步执行,CPU按跟踪方式执行)
    6.由中断类型号,查中断向量表获得中断服务子程序的入口地址
    7.CPU执行中断子程序
    8.开中断
    9.IRET指令使EIP CS HE EFR弹出栈,返回被中断的源程序



中断挂起:

可以理解为“等待响应”



其中教程中有个ICER寄存器用于中断除能,这里要专门设置一个寄存器 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。具体为什么这么设计,看下面这幅图


下图也是经常用到,放这里,免得总是去翻


原子哥代码中: void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)这个函数用来设置中断分组,

主要用于是对SCB->AIRCR=temp; 这条代码的操作,SCB->AIRCR是应用程序中断及复位控制寄存器,第8到第10位用于分组设置

 

NVIC_Init()进行了5步操作

1、设置分组

2、设置抢占优先级

3、设置响应优先级

4、设置通道

5、使能中断位

 

Ex_NVIC_Config()外部中断的配置

1、使能IO口复用时钟

2、设置中断寄存器组(总共4个组,每个组可以对应4个IO,总共可以对应16个IO(0~15)),以及使能的位

3、选择触发方式

4、开启中断

顺便看了下一些寄存器对应地址的方法,主要是分几步#define将各个基地址进行相加,在得到最后的地址后将地址强制转换为结构体指针类型,由于结构体的顺序结构可以将一段地址分配到对应的字节数里面,使用的时候直接用结构体指针指向就行了,非常方便,希望以后能学会使用这种方法写代码。

此论坛大神解释的事件与中断的区别:
事件:是表示检测到某一动作(电平边沿)触发事件发生了。
中断:有某个事件发生并产生中断,并跳转到对应的中断处理程序中。
事件可以触发中断,也可以不触发
中断有可能被更优先的中断屏蔽,事件不会

事件本质上就是一个触发信号,是用来触发特定的外设模块或核心本身(唤醒).
事件只是一个触发信号(脉冲),而中断则是一个固定的电平信号

 

 

Usart.c

UART:通用异步收发器
USART:通用同步异步收发器。一般而言,单片机中,名称为UART的接口一般只能用于异步串行通讯,而名称为USART的接口既可以用于同步串行通讯,也能用于异步串行通讯。

同步通信与异步通信区别:
1.同步通信要求接收端时钟频率和发送端时钟频率一致,发送端发送连续的比特流;异步通信时不要求接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。
2.同步通信效率高;异步通信效率较低。

3.同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。
4.同步通信可用于点对多点;异步通信只适用于点对点。



串口部分寄存器跟库函数版本还有差别,用库函数完全不用关心,只管直接设波特率就好了,我也不知道还有USART_BRR这个寄存器


以下是计算BRR寄存器值的公式,给了这条公式直接套就可以得出数值了



原子哥教程中也有提醒到在某些波特率和PCLK2频率下,还是会存在误差的,如下图


串口初始化过程:void uart_init(u32 pclk2, u32 bound)

1、先根据公式计算得出USARTDIV的小数和整数部分

2、使能PORTA口时钟

3、使能串口时钟

4、IO口状态设置

5、复位串口1

6、停止复位

7、波特率设置(有上面得出的值直接赋给BRR寄存器)

8、设置为1位停止,无校验位

9、如果使能了接收还需要使能串口中断,设置中断配置

 

 

STM32时钟系统

采用一个系统时钟不是很简单吗?为什么 STM32 要有多个时钟源呢? 因为首先
STM32 本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比
如看门狗以及 RTC 只需要几十 k 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干
扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。

 

顺便记多一个图

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-5 22:11:58 | 显示全部楼层
回复【25楼】229382777@qq.com:
---------------------------------
1.可屏蔽中断中“或者由由转台寄存器中的通用中断使能位GIE一齐关闭”读不通
2.中断过程的总结中“简述:保存断点入栈,响应经中断控制器8259优先级判断后的中断请求,恢复断点”,8259手抖了么
3.中断过程的总结是参考哪里的,是否有电子图文可以分享一下,感觉第八点开中断有些突兀,换为“中断执行完毕,清标志位,重新等待中断”可能更容易理解
4.“NVIC_Init()进行了4部操作”小学数学和语文都不怎么好的感觉,说四“步”写五点
5.外部中断最好先设置触发方式再开启

楼主又向前迈进了一步~~~~~
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-5 22:54:33 | 显示全部楼层
回复【25楼】229382777@qq.com:
---------------------------------
 不错啊。。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-5 23:18:31 | 显示全部楼层
回复【26楼】龙之谷:
---------------------------------
谢谢大神,有点晚了,刚看到,明天把问题改回来,明天开启班级两天一夜游,如果来得及还会更新一帖出来,再次感谢你那么认真看我的笔记,谢谢!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-5 23:19:05 | 显示全部楼层
回复【27楼】正点原子:
---------------------------------
谢谢原子哥鼓励
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-5 23:29:24 | 显示全部楼层
回复【26楼】龙之谷:
---------------------------------
考虑有外部硬件设备的中断,8259A芯片是一个中断管理芯片,中断的来源除了来自于硬件自身的NMI中断和来自于软件的INT n指令造成的软件中断之外,还有来自于外部硬件设备的中断,这些中断是可屏蔽的。这些中断也都通过PIC(Programmable Interrupt Controller)进行控制,并传递给CPU。
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-5 23:32:57 | 显示全部楼层
回复【28楼】229382777@qq.com:
---------------------------------
快乐地玩耍去吧,如果有时间有兴趣更新当然很好,有其他事情不用给自己安排太紧张,轻松快乐地学习最重要

祝玩得快乐
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-6 11:32:27 | 显示全部楼层

实战LED

 
LED灯发光原理
LED(Light Emitting Diode),发光二极管,是一种能够将电能转化为可见光的固态的半导体器件,它可以直接把电转化为光。LED的心脏是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,另一端连接电源的正极,使整个晶片被环氧树脂封装起来。半导体晶片由两部分组成,一部分是P型半导体,在它里面空穴占主导地位,另一端是N型半导体,在这边主要是电子。但这两种半导体连接起来的时候,它们之间就形成一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,这就是LED灯发光的原理。而光的波长也就是光的颜色,是由形成P-N结的材料决定的。

引用坛友的资料:IO口状态

1、浮空输入:浮空(floating)就是逻辑器件的输入引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。 通俗讲就是让管脚什么都不接,浮空着。

2、模拟输入:模拟输入是指传统方式的输入.数字输入是输入PCM数字信号,0,1的二进制数字信号,通过数模转换,转换成模拟信号,经前级放大进入功率放大器,功率放大器还是模拟的。

3、推挽输出:可以输出高,低电平,连接数字器件; 推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源低定。

4、开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内).

5、复用开漏输出、复用推挽输出:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)。

 

STM32中选用IO模式

1浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX

2带上拉输入_IPU——IO内部上拉电阻输入

3带下拉输入_IPD—— IO内部下拉电阻输入

4模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电

5开漏输出_OUT_OD ——IO输出0GNDIO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51IO双向功能

6推挽输出_OUT_PP ——IO输出0-GND IO输出1 -VCC,读输入值是未知的

7复用功能的推挽输出_AF_PP ——片内外设功能(I2CSCL,SDA)(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS

灌电流,拉电流

拉电流和灌电流是衡量电路输出驱动能力(注意:拉、灌都是对输出端而言的,所以是驱动能力)的参数,这种说法一般用在数字电路中。
由于数字电路的输出只有高、低(0,1)两种电平值,高电平输出时,一般是输出端对负载提供电流,其提供电流的数值叫“拉电流”;低

电平输出时,一般是输出端要吸收负载的电流,其吸收电流的数值叫“灌(入)电流”。

1、逻辑门输出为高电平时的负载电流(为拉电流)。
2、逻辑门输出为低电平时的负载电流(为灌电流)。
3、逻辑门输入为高电平时的电流(为灌电流)。
4、逻辑门输入为低电平时的电流(为拉电流)。

LED灯电路



原子哥板子上的LED接的限流电阻为510欧,通过欧姆定律可以得出加在LED灯上的电流为:3.3/510约等于0.006A即6mA,一般的LED灯5~10mA就能正常亮,所以6mA刚刚好。



以下IO口的电流限制,如果有很多灯的话需要加驱动电路



Flash、RAM(下面有关于ROMRAMEEPROMFLASH区别的资料,附件中)

以下是大神的提供的资料(但是自己运行了一下主函数里面什么都不放也会存在RW=8的情况,这8个字节是Flash的选项字节?)


Code : 代码的大小

RO:常量所占空间

RW:程序中已经初始化的变量所占空间

ZI:未初始化的static变量和全局变量以及堆栈所占的空间

上述参数和芯片Flash以及SRAM的对应关系是

Flash占用大小=Code+RO+RW

SRAM占用大小=RW+ZI


自己在写灯程序的时候发现库函数和寄存器的操作基本一致,但是也看了下官方怎么打包寄存器的,发现这种方式打包方式非常值得学习,后期学着使用,首先最常出现的依旧是assert_param,这个已经有论坛的大神解释过了,以下附上他的解释

断言机制函数assert_param
      我们在分析库函数的时候,几乎每一个函数的原型有这个函数assert_param();下面以assert_param(IS_GPIO_ALL_PERIPH(GPIOx));为例说一下我的理解,函数的参数IS_GPIO_ALL_PERIPH(GPIOx),我们可以寻找到原型
     #define IS_GPIO_ALL_PERIPH(PERIPH) (((*(uint32_t*)&(PERIPH)) == GPIOA_BASE) || \
                                    ((*(uint32_t*)&(PERIPH)) == GPIOB_BASE) || \
                                    ((*(uint32_t*)&(PERIPH)) == GPIOC_BASE) || \
                                    ((*(uint32_t*)&(PERIPH)) == GPIOD_BASE) || \
                                    ((*(uint32_t*)&(PERIPH)) == GPIOE_BASE) || \
                                    ((*(uint32_t*)&(PERIPH)) == GPIOF_BASE) || \
                                    ((*(uint32_t*)&(PERIPH)) == GPIOG_BASE))
这个宏定义的作用就是检查参数PERIPH,判断参数PERIPH是否为GPIOX(A...G)基址中的一个,只要有一个为真则其值为真,否则为假,不用多说,这是C语言中基本的逻辑运算。当然这个库函数也用的很有意思,看:首先对PERIPH进行取址,也就是求地址,&ERIPH,然后对这个地址强制转化为32位的指针,即前面加(uint32_t *),然后通过*进行访问这个地址(指针)中的内容。不多说了,看几遍就能明白。
       下面我们再回到assert_param这个函数,这个函数是哪里的呢?在stm32f10x_conf.h寻找到原型如下:
#ifdef  USE_FULL_ASSERT

    #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
    void assert_failed(uint8_t* file, uint32_t line);
#else
    #define assert_param(expr) ((void)0)
#endif 
这是一个预编译文件,若是定义了USE_FULL_ASSERT这个文件,则执行后面的文件,我们在程序中一般都没什么定义,即执行后面这个语句((void)0),这个语句不用多想,没有定义USE_FULL_ASSERT就是什么也不执行。说的明白点,对上面的那个语句IS_GPIO_ALL_PERIPH(GPIOx)不执行任何操作。
   若是定义了USE_FULL_ASSERT它,我们调用这个函数assert_param时,及对参数IS_GPIO_ALL_PERIPH(GPIOx)的正确性进行检查,通过一个C语言中的三目运算符来判断,若是返回1,执行语句(void)0,跟上面一样,若是返回0,则执行后面的函数assert_failed((uint8_t *)__FILE__, __LINE__),函数的作用在库函数中有解释,用来指示出错的行数和文件。注意:__FILE__, __LINE__是标准库函数中的宏定义!切记
void assert_failed(uint8_t* file, uint32_t line);刚开始没看明白为什么加在这里,仔细一想是在头文件的函数声明。至于函数实体呢?我们从官方文件的模板中main.c中可以找到。如下:
   void assert_failed(u8* file, u32 line) 
{ /* User can add his own implementation to report the file name and line number, 
   ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 
/* Infinite loop */ 
while (1) { } 
} 英文注释也说明了怎么应用,通过输入参数来确定位置,最简单的方法就是串口打印了,这个函数的主要思想是在输入参数有问题的时候,但是有编译不出来,它可以帮你检查参数的有效性,好处不必多言,自己领悟就行。


自己看了下库函数打包寄存器的方式:(自己解析的,难免有错,还望指出)


先用#define打包了基地址

#define PERIPH_BASE           ((uint32_t)0x40000000)

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)

GPIOA_BASE = (APB2PERIPH_BASE + 0x0800) = PERIPH_BASE + 0x10000 + 0x0800 = 0x40000000 + 0x10000 + 0x0800

                      = 0x40010800


STM32中文参考手册2.3可以查到寄存器组的起始地址



((GPIO_TypeDef *) GPIOA_BASE)   //这里将GPIOA基地址强制转为了GPIO_TypeDef这个类型的指针

//看看GPIO_TypeDef这个结构体,成员按照定义时的顺序依次存储在连续的内存空间。

typedef struct

{

  __IO uint32_t CRL;      //偏移量0x00

  __IO uint32_t CRH;      //偏移量0x04

  __IO uint32_t IDR;       //偏移量0x08

  __IO uint32_t ODR;      //偏移量0x0C

  __IO uint32_t BSRR;     //偏移量0x10

  __IO uint32_t BRR;      //偏移量0x14

  __IO uint32_t LCKR;     //偏移量0x18

} GPIO_TypeDef;

这样每操作一个结构体成员就达到了操作对应寄存器的地址跟寄存器可以对应上




先附上代码

GPIO_InitTypeDef  GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;                         //LED0-->B.5 端口配置

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;          //推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //IO口速度为50MHz

GPIO_Init(GPIOA, &GPIO_InitStructure);                               //根据设定参数初始化GPIOB.5

GPIO_SetBits(GPIOA,GPIO_Pin_8);                                        //PB.5 输出高

// GPIO_InitTypeDef这个结构体用于定义个GPIO口,对应引脚、速度、模式

typedef struct

{

  uint16_t GPIO_Pin;          

  GPIOSpeed_TypeDef GPIO_Speed; 

  GPIOMode_TypeDef GPIO_Mode;  

}GPIO_InitTypeDef;


GPIO_Pin:

//只要引脚号不为0则宏定义的值为1,注:#define GPIO_Pin_0 ((uint16_t)0x0001)  引脚号从1开始

#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))

 

//宏定义GPIO所有可能的值,如果不在这个范围内会通过assert_param报警

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \

                               ((PERIPH) == GPIOB) || \

                               ((PERIPH) == GPIOC) || \

                               ((PERIPH) == GPIOD) || \

                               ((PERIPH) == GPIOE) || \

                               ((PERIPH) == GPIOF) || \

                               ((PERIPH) == GPIOG))

//宏定义引脚对应值

#define GPIO_Pin_0                 ((uint16_t)0x0001) //至于这里为什么是以移位设置而不是递增的方式设置跟库函数有关,下面程序有介绍

#define GPIO_Pin_1                 ((uint16_t)0x0002)

#define GPIO_Pin_2                 ((uint16_t)0x0004) 

#define GPIO_Pin_3                 ((uint16_t)0x0008)

#define GPIO_Pin_4                 ((uint16_t)0x0010)

…………

GPIO_Speed:是个枚举GPIOSpeed_TypeDef类型

typedef enum

{

  GPIO_Speed_10MHz = 1,

  GPIO_Speed_2MHz = 2,

  GPIO_Speed_50MHz = 3

}GPIOSpeed_TypeDef;

GPIO_Speed 只能是在这个枚举范围内选值

 

GPIO_Mode:同样是个枚举GPIOMode_TypeDef类型

typedef enum

{ GPIO_Mode_AIN = 0x0,           //模拟输入模式

  GPIO_Mode_IN_FLOATING = 0x04, //浮空输入模式(复位后的状态)

  GPIO_Mode_IPD = 0x28,         //下拉输入模式

  GPIO_Mode_IPU = 0x48,         //上拉输入模式

  GPIO_Mode_Out_OD = 0x14,   //通用开漏输出模式,至于这里为什么第五位都为1,下面程序中介绍

  GPIO_Mode_Out_PP = 0x10,    //通用推挽输出模式

  GPIO_Mode_AF_OD = 0x1C,    //复用功能开漏输出模式

  GPIO_Mode_AF_PP = 0x18      //复用功能推挽输出模式

}GPIOMode_TypeDef;

GPIO_Mode 只能是在这个枚举范围内选值

对应模式设置如下寄存器



接来下解析一下void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)这个函数

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)    

{   //形参为两个结构体指针类型,上面已经介绍了,一个用于指定GPIO,一个用于初始状态包括速度,模式设置

  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;

  uint32_t tmpreg = 0x00, pinmask = 0x00;

//currentmode:模式设置(对应CRH和CRL的32个bit)

//currentpin:保存引脚号

//pinpos:引脚扫描变量,分为扫描GPIO的0~7和8~15引脚

//pos:循环扫描8次,与上面的currentpin比较,相同即对相应的引脚进行设置

//tmpreg:保存上次设置的值不变,跟这次引脚号的设置不冲突,进行&和|运算以后赋给寄存器CRL

//pinmask:对应指定IO口的CNF和MODE位

 

  /* Check the parameters */

  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));                  //检测参数准确性,不在范围内会报错,上面有对assert_param的介绍

  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));   //检测参数准确性

  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));        //检测参数准确性

 

/*---------------------------- GPIO Mode Configuration -----------------------*/

  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); //记录输入输出和模式

  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)

  { //如果为输出模式则记录下设置的速度,这就是为什么上面定义的时候第五位都是1的原因,用于分开输入和输出标志

    /* Check the parameters */

    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); //检测参数准确性

    /* Output mode */

    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;     //保存设置的速度

  }

/*---------------------------- GPIO CRL Configuration ------------------------*/

  /* Configure the eight low port pins */

  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)

  {//如果是GPIO0~7端口,则设置CRL寄存器,0X00FF代表后面8位为1,这就是为什么上面#define那么定义引脚号的原因,而不用递增

    tmpreg = GPIOx->CRL;    //保存上次的设置

    for (pinpos = 0x00; pinpos < 0x08; pinpos++)//循环8,依次对应GPIO07

    {

      pos = ((uint32_t)0x01) << pinpos;   //对应8IO,依次遍历

      /* Get the port pins position */

      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;  //获取引脚位置

      if (currentpin == pos) //找到了对应的引脚,执行下面的设置

      {

        pos = pinpos << 2;  //向左移两位为x2,即对应到指定IO4bit(CNFMODE)的寄存器

        /* Clear the corresponding low control register bits */

        pinmask = ((uint32_t)0x0F) << pos;   //对应到所指定IO口的CNFMODE,

        tmpreg &= ~pinmask;              //先清空对应的CNFMODE

        /* Write the mode configuration in the corresponding bits */

        tmpreg |= (currentmode << pos);    //然后配置对应IO的寄存器模式

        /* Reset the corresponding ODR bit */

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) //如果对应为下拉输入模式

        {

          GPIOx->BRR = (((uint32_t)0x01) << pinpos);       //清除对应端口,即实现下拉的效果

        }

        else

        {

          /* Set the corresponding ODR bit */

          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) //如果对应为上拉输入模式

          {

            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);      //设置对应端口为1,即实现对应上拉的效果

          }

        }

      }

    }

    GPIOx->CRL = tmpreg;//将设置好的值赋给CRL寄存器

  }
//下面代码几乎和上面没差,用于设置CRH

/*---------------------------- GPIO CRH Configuration ------------------------*/

  /* Configure the eight high port pins */

  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)

  {

    tmpreg = GPIOx->CRH;

    for (pinpos = 0x00; pinpos < 0x08; pinpos++)

    {

      pos = (((uint32_t)0x01) << (pinpos + 0x08));

      /* Get the port pins position */

      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);

      if (currentpin == pos)

      {

        pos = pinpos << 2;

        /* Clear the corresponding high control register bits */

        pinmask = ((uint32_t)0x0F) << pos;

        tmpreg &= ~pinmask;

        /* Write the mode configuration in the corresponding bits */

        tmpreg |= (currentmode << pos);

        /* Reset the corresponding ODR bit */

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)

        {

          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));

        }

        /* Set the corresponding ODR bit */

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)

        {

          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));

        }

      }

    }

    GPIOx->CRH = tmpreg;

  }

}


附上小灯程序(在寄存器工程里面将官方的库函数移植了进来,没有添加任何的库函数包,下面是点灯的几种方式,以后也希望按着库函数这种编程方式写代码)


[mw_shl_code=c,true]#include "sys.h" #include "delay.h" #include "led.h" int main(void) { Stm32_Clock_Init(9); //系统时钟设置,9倍频,72M delay_init(72);//systick使用系统时钟72M的8分频,9Mhz LED_Init(); LED_Init1(); while(1) { /***************1**********************/ GPIOA->ODR &= 0XFFFFFEFF; GPIOA->ODR |= 0<<8; GPIOD->BSRR &= 0XFFFDFFFF; GPIOD->BSRR |= 1<<18; delay_ms(200); GPIOA->ODR &= 0XFFFFFEFF; GPIOA->ODR |= 1<<8; GPIOD->BSRR &= 0XFFFFFFFD; GPIOD->BSRR |= 1<<2; delay_ms(200); /***************2**********************/ LED0 = 0; LED1 = 0; delay_ms(200); LED0 = 1; LED1 = 1; delay_ms(200); /***************3**********************/ GPIO_ResetBits(GPIOA,GPIO_Pin_8); GPIO_SetBits(GPIOD,GPIO_Pin_2); delay_ms(300); GPIO_SetBits(GPIOA,GPIO_Pin_8); GPIO_ResetBits(GPIOD,GPIO_Pin_2); delay_ms(300); } } [/mw_shl_code]




rom,ram,eeprom,flash区别.docx

25.85 KB, 下载次数: 4298

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2015-11-6 12:57:54 | 显示全部楼层
回复【33楼】229382777@qq.com:
---------------------------------
支持楼主继续写,坚持下来
我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-6 13:10:51 | 显示全部楼层
回复【34楼】Admin:
---------------------------------
谢谢站长
我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-6 16:37:49 | 显示全部楼层
LED发光电路中发光二极管压降一般按2V计算,这个一般不可忽略,发光二极管电流一般不要超过10ma,2~5ma有明显变化,超过5ma亮度变化不大(印象中听金沙滩小宋老师讲的)

开始点灯了,方式新奇,很不错~~~~~
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-6 17:25:13 | 显示全部楼层
回复【36楼】龙之谷:
---------------------------------
嗯,谢谢
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-8 20:19:17 | 显示全部楼层

实战:按键

按键消抖
通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹
性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,
而是在闭合和断开的瞬间伴随了一连串的抖动,如下图所示。


按键稳定闭合时间长短是由操作人员决定的, 通常都会在 100ms 以上,刻意快速按的话
能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms
以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。
当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。
硬件消抖:就是在按键上并联一个电容,如图下图所示,利用电容的充放电特性来对抖动
过程中产生的电压毛刺进行平滑处理,从而实现消抖。但实际应用中,这种方式的效果往往
不是很好,而且还增加了成本和电路复杂度,所以实际中使用的并不多。


库函数和寄存器版本的按键这一章讲的都差不多,目前我能力有限,也找不出比原子哥更好的按键程序,基本都是差不多的,后期学了串口和定时器中断那些再写个比较综合点的程序。下面附上很好的状态机编程的资料,有需要的可以了解学习一下。

C语言中状态机编程.docx

20.76 KB, 下载次数: 4420

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-8 22:07:10 | 显示全部楼层
回复【38楼】229382777@qq.com:
---------------------------------
不错,继续坚持啊.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-9 07:59:38 | 显示全部楼层
回复【39楼】正点原子:
---------------------------------
谢谢原子哥支持
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-9 22:50:59 | 显示全部楼层
回复【41楼】229382777@qq.com:
---------------------------------
继续支持你啊。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-10 07:48:54 | 显示全部楼层
回复【42楼】正点原子:
---------------------------------
谢谢老大
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-10 10:52:35 | 显示全部楼层

实战:外部中断

 

前面SYSTEM(二)有过一部分中断的总结了,这里再补充一下

 

EXTI控制器的主要特性如下:
● 每个中断/事件都有独立的触发和屏蔽
● 每个中断线都有专用的状态位
● 支持多达20个软件的中断/事件请求
● 检测脉冲宽度低于APB2时钟宽度的外部信号。

 

要把 IO 口作为外部中断输入,有以下几个步骤:

1)初始化 IO 口为输入。
2
)开启 IO 口复用时钟,设置 IO 口与中断线的映射关系。(需要启动复用时钟)
3
)开启与该IO口相对的线上中断/事件,设置触发条件。
4
)配置中断分组(NVIC),并使能中断。
5
)编写中断服务函数。



[mw_shl_code=c,true]//外部中断初始化程序 //初始化PA0,PC5,PA15为中断输入. void EXTI_Init(void) { KEY_Init(); //设置时钟以及IO口 Ex_NVIC_Config(GPIO_A,0,RTIR); //上升沿触发 Ex_NVIC_Config(GPIO_C,5,FTIR); //下降沿触发 Ex_NVIC_Config(GPIO_A,15,FTIR); //下降沿触发 MY_NVIC_Init(2,2,EXTI0_IRQn,2); //抢占2,子优先级2,组2 MY_NVIC_Init(2,1,EXTI9_5_IRQn,2); //抢占2,子优先级1,组2 MY_NVIC_Init(2,0,EXTI15_10_IRQn,2); //抢占2,子优先级0,组2 } //外部中断配置函数 //只针对GPIOA~G;不包括PVD,RTC和USB唤醒这三个 //参数: //GPIOx:0~6,代表GPIOA~G //BITx:需要使能的位; //TRIM:触发模式,1,下升沿;2,上降沿;3,任意电平触发 //该函数一次只能配置1个IO口,多个IO口,需多次调用 //该函数会自动开启对应中断,以及屏蔽线 void Ex_NVIC_Config(u8 GPIOx,u8 BITx,u8 TRIM) { u8 EXTADDR; u8 EXTOFFSET; EXTADDR=BITx/4;//得到中断寄存器组的编号,AFIO_EXTICR分4组分别代表16个IO,EXTADDR代表哪个组(1~4) EXTOFFSET=(BITx%4)*4; //一个IO占4个位,EXTOFFSET用于指定哪个IO RCC->APB2ENR|=0x01;//使能io复用时钟 AFIO->EXTICR[EXTADDR]&=~(0x000F<<EXTOFFSET);//清除原来设置!!! AFIO->EXTICR[EXTADDR]|=GPIOx<<EXTOFFSET;//EXTI.BITx映射到GPIOx.BITx //对应库函数:GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5); //自动设置 EXTI->IMR|=1<<BITx;// 开启line BITx上的中断请求 if(TRIM&0x01)EXTI->FTSR|=1<<BITx;//line BITx上事件下降沿触发 if(TRIM&0x02)EXTI->RTSR|=1<<BITx;//line BITx上事件上升降沿触发 } //设置NVIC //NVIC_PreemptionPriority:抢占优先级 //NVIC_SubPriority :响应优先级 //NVIC_Channel :中断编号 //NVIC_Group :中断分组 0~4 //注意优先级不能超过设定的组的范围!否则会有意想不到的错误 //组划分: //组0:0位抢占优先级,4位响应优先级 //组1:1位抢占优先级,3位响应优先级 //组2:2位抢占优先级,2位响应优先级 //组3:3位抢占优先级,1位响应优先级 //组4:4位抢占优先级,0位响应优先级 //NVIC_SubPriority和NVIC_PreemptionPriority的原则是,数值越小,越优先 void MY_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel,u8 NVIC_Group) { u32 temp; MY_NVIC_PriorityGroupConfig(NVIC_Group);//设置分组 temp=NVIC_PreemptionPriority<<(4-NVIC_Group);//4代表0~4五组,抢占优先级占几位这里左移几位(在高2位) temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);//响应优先级占几位,这里就右移几位(在低2位) temp&=0xf;//取低四位 NVIC->ISER[NVIC_Channel/32]|=(1<<NVIC_Channel%32);//使能中断位(要清除的话,相反操作就OK) NVIC->IP[NVIC_Channel]|=temp<<4;//设置响应优先级和抢断优先级,配置高4位 } //设置NVIC分组 //NVIC_Group:NVIC分组 0~4 总共5组 void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group) { u32 temp,temp1; temp1=(~NVIC_Group)&0x07;//取后三位 temp1<<=8; temp=SCB->AIRCR; //读取先前的设置 temp&=0X0000F8FF; //清空先前分组 temp|=0X05FA0000; //写入钥匙 temp|=temp1; SCB->AIRCR=temp; //设置分组 } [/mw_shl_code]


我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-10 19:38:53 | 显示全部楼层


(字符补丁)
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-10 21:18:47 | 显示全部楼层
本帖最后由 1208 于 2019-11-21 18:42 编辑

<p class="MsoNormal">
        <span style="color:#E53333;font-size:16px;"><strong>实战:独立、窗口看门狗</strong></span><span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">&nbsp;<br>
<span style="color:#E53333;"><strong></strong></span><span style="color:#E53333;"><strong>看门狗简介:</strong></span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;"><span style="color:#E53333;">STM32F10xxx内置两个看门狗,提供了更高的安全性、时间的精确性和使用的灵活性。</span>两个看门狗设备(独立看门狗和窗口看门狗)可用来检测和解决由软件错误引起的故障;当计数器达到给定的超时值时,触发一个中断(仅适用于窗口型看门狗)或产生系统复位。</span><br>
<span style="font-size:16px;"> 独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它也仍然有效。窗口看门狗由从APB1时钟分频后得到的时钟驱动,通过可配置的时间窗口来检测应用程序非正常的过迟或过早的操作。</span><br>
<span style="font-size:16px;"> IWDG最适合应用于那些需要看门狗作为一个在主程序之外,能够完全独立工作,并且对时间精度要求较低的场合。WWDG最适合那些要求看门狗在精确计时窗口起作用的应用程序。</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">&nbsp;</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;">独立看门狗和窗口看门狗的区别</span>
</p>
<span style="font-size:16px;">1)独立看门狗没有中断,窗口看门狗有中断</span><br>
<span style="font-size:16px;"> 2)独立看门狗有硬件软件之分,窗口看门狗只能软件控制</span><br>
<span style="font-size:16px;"> 3)独立看门狗只有下限,窗口看门狗又下限和上限</span><br>
<span style="font-size:16px;"> 4)独立看门狗是12位递减的。窗口看门狗是7位递减的</span><br>
<span style="font-size:16px;"> 5)独立看门狗是用的内部的大约40KHZ RC振荡器,窗口看门狗是用的系统时钟APB1ENR</span><br>
<br>
<br>
<span style="color:#E53333;font-size:16px;"><strong>独立看门狗</strong></span><br>
<br>
<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;">独立看门狗特性:</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">● 自由运行的递减计数器</span><br>
<span style="font-size:16px;"> ● 时钟由独立的RC振荡器提供(可在停止和待机模式下工作)</span><br>
<span style="font-size:16px;"> ● 看门狗被激活后,则在计数器计数至0x000时产生复位</span><span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">&nbsp;</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;">独立看门狗的使能:</span><span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">STM32的独立看门狗可以是硬件使能或软件使能,硬件使能是通过设置选择字节配置,软件使能是通过软件设置寄存器位配置。</span><br>
<span style="font-size:16px;"> 硬件使能的看门狗,在系统上电之后就开始工作,一旦开始工作就没有办法让它停止工作;而软件使能的看门狗,只有在设置了相应的寄存器位后才能开始工作,一旦开始工作,只有系统复位才能让它停止工作。</span><br>
<span style="font-size:16px;"> 如果启用了硬件使能,必须通过擦除相应的选择字节位关闭硬件看门狗功能,这时看门狗的功能可以通过软件使能。</span><span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">&nbsp;<br>
<span style="color:#E53333;">独立看门狗的时钟:</span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;">STM32 的独立看门狗由内部专门的 40Khz 低速时钟驱动,即使主时钟发生故障,它也仍然</span><br>
<span style="font-size:16px;"> 有效。这里需要注意独立看门狗的时钟是一个内部 RC 时钟,所以并不是准确的 40Khz,而是</span><br>
<span style="font-size:16px;"> 在
30~60Khz 之间的一个可变化的时钟,只是我们在估算的时候,以 40Khz 的频率来计算,看</span><br>
<span style="font-size:16px;"> 门狗对时间的要求不是很精确,所以,时钟有些偏差,都是可以接受的。</span>
</p>

<br>
<br>
<br>
<span style="font-size:16px;"> <span style="color:#E53333;">独立看门口需要配置的几个寄存器</span></span><br>

<br>
<p class="MsoNormal">
        <span style="font-size:16px;color:#000000;"><span style="color:#003399;">键值寄存器
IWDG_KR:</span> 在键寄存器(IWDG_KR)中写入
0xCCCC,开始启用独立看门狗;此时计数器开始从其复位值 0xFFF 递减计数。当计数器计数到末尾 0x000 时,会产生一个复位信号(IWDG_RESET)。 无论何时,只要键寄存器 IWDG_KR 中被写入
0xAAAA,
IWDG_RLR 中的值就会被重新加载到计数器中从而避免产生看门狗复位(称为喂狗) 。</span>
</p>

<br>
<br>

<br>
<br>

<br>
<br>
<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;">独立看门狗框图:</span>
</p>

<br>
<span style="line-height:1.5;font-size:16px;color:#E53333;"><br>
下面附上程序,独立看门狗的配置程序比较简单,配置好后只需要按键不断的喂狗就可以避免单片机复位了</span>
<p class="MsoNormal">
        <span></span>
</p>
<div style="background-color:#E8E8E8;">
[mw_shl_code=c,true]void IWDG_Init(u8 prer,u16 rlr)
{
        IWDG-&gt;KR=0X5555;//使能对IWDG-&gtR和IWDG-&gt;RLR的写                                                                                                   
          IWDG-&gtR=prer;  //设置分频系数   
          IWDG-&gt;RLR=rlr;  //重加载寄存器 IWDG-&gt;RLR  
        IWDG-&gt;KR=0XAAAA;//reload                                                                                          
          IWDG-&gt;KR=0XCCCC;//使能看门狗       
}
//喂独立看门狗
void IWDG_Feed(void)
{
        IWDG-&gt;KR=0XAAAA;//reload                                                                                          
}
[/mw_shl_code]
</div>
<br>
<span style="color:#E53333;font-size:16px;"><strong>窗口看门狗<br>
</strong><br>
简介:</span><br>
<p class="MsoNormal">
        <span style="font-size:16px;color:#000000;">窗口看门狗(
WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序</span><br>
<span style="font-size:16px;color:#000000;"> 背离正常的运行序列而产生的软件故障。除非递减计数器的值在 T6 位 ( WWDG-&gt;CR 的第六位)</span><br>
<span style="font-size:16px;color:#000000;"> 变成 0 前被刷新,看门狗电路在达到预置的时间周期时,会产生一个 MCU 复位。在递减计数</span><br>
<span style="font-size:16px;color:#000000;"> 器达到窗口配置寄存器(WWDG-&gt;CFR)数值之前,如果 7 位的递减计数器数值(在控制寄存器中)</span><br>
<span style="font-size:16px;color:#000000;"> 被刷新, 那么也将产生一个 MCU 复位。这表明递减计数器需要在一个有限的时间窗口中被刷新</span><br>
<span style="line-height:1.5;font-size:16px;color:#000000;">他们的关系可以用下图表示</span><span style="line-height:1.5;"></span>
</p>

<br>
<br>
<span style="color:#E53333;"></span><span style="line-height:1.5;color:#E53333;font-size:16px;">WWDG主要特性</span>
<p class="MsoNormal">
        <span style="font-size:16px;color:#000000;">● 可编程的自由运行递减计数器</span><span><br>
</span><span style="font-size:16px;color:#000000;">● 条件复位</span><span><br>
<span style="font-size:16px;color:#000000;"> ─ </span></span><span style="font-size:16px;color:#000000;">当递减计数器的值小于</span><span style="font-size:16px;color:#000000;">0x40</span><span style="font-size:16px;color:#000000;">, </span><span style="font-size:16px;color:#000000;">(</span><span style="font-size:16px;color:#000000;">若看门狗被启动</span><span style="font-size:16px;color:#000000;">)</span><span style="font-size:16px;color:#000000;">则产生复位。</span><span><br>
<span style="font-size:16px;color:#000000;"> ─ </span></span><span style="font-size:16px;color:#000000;">当递减计数器在窗口外被重新装载, </span><span style="font-size:16px;color:#000000;">(</span><span style="font-size:16px;color:#000000;">若看门狗被启动</span><span style="font-size:16px;color:#000000;">)</span><span style="font-size:16px;color:#000000;">则产生复位。见</span><span style="font-size:16px;color:#000000;">0</span><span style="font-size:16px;color:#000000;">。</span><span><br>
</span><span style="font-size:16px;color:#000000;">● 如果启动了看门狗并且允许中断,当递减计数器等于</span><span style="font-size:16px;color:#000000;">0x40</span><span style="font-size:16px;color:#000000;">时产生早期唤醒中断</span><span style="font-size:16px;color:#000000;">(EWI)</span><span style="font-size:16px;color:#000000;">,它可</span><span><br>
</span><span style="font-size:16px;color:#000000;">以被用于重装载计数器以避免</span><span style="font-size:16px;color:#000000;">WWDG</span><span style="font-size:16px;color:#000000;">复位。</span><span></span>
</p>
<br>
<span style="color:#E53333;font-size:16px;">窗口看门狗的几个寄存器:</span><br>
<span style="color:#E53333;"></span><span style="color:#E53333;font-size:16px;">控制寄存器</span><span style="color:#E53333;font-size:16px;">(WWDG_CR)</span><span style="color:#E53333;"></span><br>

<br>
<br>
<span style="color:#E53333;"></span><span style="line-height:1.5;color:#E53333;font-size:16px;">配置寄存器(WWDG_CFR)</span><br>

<br>
<br>
<span style="color:#E53333;"></span><span style="line-height:1.5;color:#E53333;font-size:16px;">状态寄存器(WWDG_SR)</span><br>

<br>
<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;">窗口看门狗配置过程</span><span></span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#000000;">1) 使能 </span><span style="font-size:16px;color:#000000;">WWDG </span><span style="font-size:16px;color:#000000;">时钟</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#000000;">2) 设置 </span><span style="font-size:16px;color:#000000;">WWDG_CFR </span><span style="font-size:16px;color:#000000;">和 </span><span style="font-size:16px;color:#000000;">WWDG_CR </span><span style="font-size:16px;color:#000000;">两个寄存器</span><br>
<span style="font-size:16px;color:#000000;"> 3) 开启 </span><span style="font-size:16px;color:#000000;">WWDG </span><span style="font-size:16px;color:#000000;">中断并分组</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#000000;">4) 编写中断服务函数</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#003399;">&nbsp;</span>
</p>
<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;">以下为配置程序,可以自己选择用中断来重设窗口值,也可以不用中断</span><span style="font-size:16px;color:#E53333;">(</span><span style="font-size:16px;color:#E53333;">亲测没有问题</span><span style="font-size:16px;color:#E53333;">)</span><span style="font-size:16px;color:#E53333;">,不过建议用中断</span><span></span>
</p>
<div style="background-color:#E8E8E8;">
[mw_shl_code=c,true]//保存WWDG计数器的设置值,默认为最大.
u8 WWDG_CNT=0x7f;
//初始化窗口看门狗        
//tr   :T[6:0],计数器值
//wr   :W[6:0],窗口值
//fprer:分频系数(WDGTB),仅最低2位有效
//Fwwdg=PCLK1/(4096*2^fprer).
void WWDG_Init(u8 tr,u8 wr,u8 fprer)
{   
        RCC-&gt;APB1ENR|=1&lt;&lt;11;         //使能wwdg时钟
        WWDG_CNT=tr&amp;WWDG_CNT;   //初始化WWDG_CNT.     
        WWDG-&gt;CFR|=fprer&lt;&lt;7;    //PCLK1/4096再除2^fprer ,CK计时器时钟除以8
        WWDG-&gt;CFR&amp;=0XFF80;      //复位窗口值
        WWDG-&gt;CFR|=wr;                     //设定窗口值      
        WWDG-&gt;CR|=WWDG_CNT;         //设定计数器值
        WWDG-&gt;CR|=1&lt;&lt;7;                  //开启看门狗      
        MY_NVIC_Init(2,3,WWDG_IRQn,2);//抢占2,子优先级3,组2     
        WWDG-&gt;SR=0X00;                         //清除提前唤醒中断标志位
        WWDG-&gt;CFR|=1&lt;&lt;9;        //使能提前唤醒中断,计数到40h,产生中断
}
//重设置WWDG计数器的值
void WWDG_Set_Counter(u8 cnt)
{
        WWDG-&gt;CR =(cnt&amp;0x7F);//重设置7位计数器
}
//窗口看门狗中断服务程序
void WWDG_IRQHandler(void)
{      
        WWDG_Set_Counter(WWDG_CNT);//重设窗口看门狗的值!         
        WWDG-&gt;SR=0X00;//清除提前唤醒中断标志位
        LED1=!LED1;
}[/mw_shl_code]
</div>
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-10 21:30:39 | 显示全部楼层
顺便跟原子哥提个建议,排版的难度有点大啊,有时候按回车会自动跳回顶部,然后又要手动拉下来,感觉挺麻烦的,希望新的论坛可以改进排版,尽量把排版变得简单些
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-10 22:56:16 | 显示全部楼层
回复【47楼】229382777@qq.com:
---------------------------------
新论坛会改善的.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-11 07:52:37 | 显示全部楼层
回复【49楼】chinafox:
---------------------------------
恩,谢谢你的建议,后期会多以写为目的
我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 06:39

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表