参考: 郭天祥:https://www.bilibili.com/video/BV1DW411a7mz?p=8
韦东山:https://www.bilibili.com/video/BV1ga4y1Y7PL?p=4
https://www.bilibili.com/video/BV17g411F7oR?spm_id_from=333.999.0.0
洋桃电子:https://www.bilibili.com/video/BV1eW411J7cf?p=2&spm_id_from=pageDriver
参考书籍:
GPIO:general peripheral input/ouput,通用的外设输入、输出接口。
这类电路通常只使用一个引脚:
门电路:与门、或门、非门
比如:UART、I2C、SPI、Nand、TFT LCD。
如果两个设备之间要传输的数据比较复杂,可以约定一些规则。这类接口被称为"协议类"接口。
当然可以只使用一条GPIO引脚来传输复杂的数据,比如红外遥控器、温度传感器等。也可使用多条线路来传输数据,比如UART、I2C、SPI等。比如TFT LCD的接口线将近30条。
例子:
I2C接口
比如:Nor Flash、SDRAM、DDR、网卡DM9000等。
数字电路上传输的电压值只有2类取值,比如
模拟电路上传输的电压可以是各种各样的,比如以下两个电路:
读取滑动电阻器上的触点电压值
GPIO可以设置为输出、输入:
输出功能
LED
发射红外信号
控制电机
蜂鸣器
数码管
输入功能
实现各类协议
怎么用一个GPIO来控制LED?换句话说,怎么让一个GPIO输出高、低电平?
芯片内部有很多模块,比如GPIO、UART(串口)。
一个引脚,可以接到模块A,也可以接到模块B,比如上图中的引脚gpio0_0,可以接到GPIO group 0,也可以接到UART。
可以设置某些寄存器(比如io_mux),选择引脚的功能。
假设一个引脚被设置成了GPIO功能,那么它是用作输出,还是输入?
在GPIO模块内部,一般都有一个方向选择寄存器,里面每一位用来控制一个引脚的方向。
比如GPIO group 0中有一个gpio0_dir_reg寄存器,
一个GPIO引脚被设置成输出,那么怎样设置它的输出电平?
一个GPIO引脚被设置成输入,那么怎样读取它的输入电平?
在GPIO模块内部,一般都有一个数据寄存器,里面每一位用来控制一个引脚的输出电平。
比如GPIO group 0中有一个gpio0_data_reg寄存器,
参考资料:图文详解二极管原理
二极管的箭头表示正向电流的方向
二极管的电流具有单向性
假设正极、负极之间的电压为V
内部结构:由PN节组成,P代表正极(positive),N代表负极(negative)
二极管中流动的是电子,电流方向是从正极到负极,电子流动的方向是从负极到正极
使用二极管
参考资料:三极管工作原理分析精辟透彻看后你就懂
可以使用二极管的特性制作成三极管,组成开关电路。
三极管实物图:
三极管可以分为:NPN三极管、PNP三极管。
三极管用作开关:
三极管用作放大:
因为基极空穴较少,所以发射极电子被集电极电场吸引进入集电极过程与基极空穴复合概率较小,当基极电流增大(空穴增多)时,因为电子与基极空穴复合概率较小,所以,基极电流稍微增大一点,就需要很多的电子才能与基极增多一点的空穴复合,因此,基极电流变化一点,而引起发射极电流发生较大的变动,从而实现了放大作用。
以NPN三极管为例:正常工作在放大状态时,因为基极电压高于发射极,电路正偏,有大量电子流入发射极(感觉是流出?),形成Ie,电子原本要通过基极回到电源正极,但是发射极电子进入基极后,由于集电极电压比基极还要高,于是电子被集电极强烈的电场吸引,从而电子不走基极回到电源正极,而进入集电极到达电源正极形成集电极电流Ic,但是,基极中还是有空穴的(比较少),发射极电子被集电极电场吸引进入集电极过程中,一小部分电子与基极空穴复合形成基极电流Ib。这就是三级管电流走向。
三极管导通,第二个三极管不导通,LED被熄灭
同步(synchronous)、异步(asynchronous),使用生活例子来说就是:
在电子产品中,使用同步信号进行传输时,一般涉及两个信号:
比如:
使用异步信号传输数据时,双方遵守相同的约定:
起始信号:发送方可以通知接收方"注意了,我要开始传输数据了"
数据的表示
以红外遥控器解码器为例,它向单片机发出的数据格式如下:
起始信号:解码器发出一个9ms的低电平、4.5ms的高电平,用来同时对方说"开始了"
表示一位数据
接收方、发送方都遵守这样的约定,就可以使用一条线传输数据
同步传输 | 异步传输 | |
---|---|---|
信号线 | 多:时钟信号、数据信号 | 少:只需要数据信号 |
速率 | 可变,提高时钟信号频率即可 | 双方提前约定 |
抗干扰能力 | 强 | 弱 |
两个设备之间,只使用一条数据线,能否传输双向的数据?
不让双方同时驱动电路,或者即使同时驱动也没关系:
真值表如下:
A | B | DATA |
---|---|---|
0 | 0 | 1(由上拉电阻决定) |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 0 |
从真值表和电路图我们可以知道:
芯片内部的三极管,被称为open collector,开集,也就是在芯片内部三极管的集电极是开放的。
芯片内部不驱动三极管是,集电极的电平由外面的上拉电阻决定。
这种电路实现了:
初始状态:一开始,双方都不驱动三极管,DATA为高
起始信号和回应:A想传输数据给B,发出开始信号、得到回应信号
传输:A发送数据给B,比如传输2位数据0、1
结束:A释放三极管,DATA变为高电平
结束:A释放三极管,DATA变为高电平
这时候,B也可以使用一样的方法给A传输数据
全英文的数据手册少则十几页,多则上百页也有,加上我们又是如此的爱国(英文水平差的借口),所以在阅读全英文数据手册的时候,根本做不到面面俱到,当然也是完全没有必要,学会善用Ctrl+F搜索关键词,按需所取,阅读我们关注的部分即可。
我以一个DC-DC BUCK芯片举例,列出了很多关键词,其他的数据手册也是同样的道理。
▉ Title
首先是Title,这也是厂家秀肌肉的地方,会告诉你一些最重要的芯片信息,比如TPS56120x系列、输入电压范围4.5~17V、最大输出电流1A、同步降压、封装是6Pin的SOT-23等。
▉ Feature
如果你是选型,以上参数符合要求,你就会接着往下去看,feature展示了更多的参数,比如输出电压范围、静态功耗、关闭功耗、精度和频率等。
▉ Description description
可以让我们对这个芯片有个大概的了解。
▉ Table of Contents
有的数据手册会有目录,可以先了解大致有那些内容,帮助我们寻找关键字。
▉Pin Configuration and Functions
可以了解芯片的管脚排布,每个管脚对应的信号名,建立原理图封装时需要参考下方这个图。
通过pin functions了解每个管脚的功能描述,以及设计电路时有什么需要注意的。
▉ Absolute Maximum
Absolute Maximum即绝对最大值,加在芯片上的参数(电压、温度、ESD等级等)绝对不能超过这个值,否则芯片会损坏。
▉ Electrical Characteristics
硬件工程师必关注的电气参数,每个芯片的电气参数也是不尽相同的。
▉ Typical Characteristics
典型的参数,指的是芯片厂商在特定的参数下,测量得出的一些芯片特性,比如下面的不同输出电压和开关频率之间的关系,DC-DC效率和输出电流之间的关系等等,这个是为了让我们更好的了解芯片的性能。
▉ Functional Block Diagram
功能框图非常重要,透过外部的管脚了解内部的组成,可以更好的理解芯片,如SW管脚接了两个MOS管,这是为什么能输出占空比的原因?OVP和UVP都是通过比较器来实现的等等。
▉ Feature Description
对芯片的某一些特性进行描述,让我们更好的理解这个芯片的相关特性,如下DC-DC的如软启动、电流保护、UVLO等功能都有详细的描述。
▉ Typical Application
对于芯片类的数据手册来说,典型应用就是参考电路图。
▉ Layout Guide
对于芯片类如DC-DC,还有layout指导。
▉ Packaging Information
package信息,指的是一盒里面的数量,如下可以看见QTY3000和QTY250的型号是不一样的。
一个系列不同的型号多在后缀有差别,可能是封装不同、package QTY的不同等,所以在order的时候需要写完整的芯片型号。
▉ Package Outline
封装尺寸信息,在建立PCB封装时会用到。
▉ Example Layout
根据提供的参考layout建立我们自己的PCB封装。
列了这么多关键词,并不是教大家如何阅读DC-DC数据手册,而是在阐明一个点:不同的人看数据手册的侧重点是不一样的,硬件工程师更关注电气参数、封装信息、参考设计等,软件工程师更关注寄存器、协议等,提取关键字,高效的阅读数据手册,找到对我们有帮助的内容才是最重要的。
2440芯片手册讲到片选地址映射图:
为什么NOR FLASH地址线从ADDR1开始?SDARM地址是从ADDR2开始的?
如果CPU要读取32位数据,那么内存控制器会去读两次16位数据存起来,一次性交给CPU。
例如查询2440芯片手册内存控制配置方法:
再查询相应的外设手册:
1602代表:显示16个字符,可以显示两行。如下图所示:
发现使用上表中的数值芯片不能正常驱动,查看原厂家芯片英文资料,实践可以正常使用,具体数值如下图所示:
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);
#endif
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//main函数入口
void main()
{
LCD_Init();
while(1);
}
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
前面讲了,器件前四位地址固定为1010,后三位在开发板里全部接地,因此器件地址确定为:0X1010 000.
IIC.h
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);
#endif
IIC.c
#include <REGX52.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;//前后要保证间隔4.7us,郭天祥老师在这里写了个delay空函数(下同)
I2C_SDA=0;
I2C_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;//单片机节省空间 不用int
temp = Byte;
for(i=0;i<8;i++)
{
temp = temp<<1;
I2C_SDA = CY;//溢出位
//I2C_SDA=Byte&(0x80>>i); 这种方法没看懂
I2C_SCL=1;
I2C_SCL=0;//低电平可改变数据 下一循环送入新的数据
}
}
/**
* @brief I2C接收一个字节
* @param 无
* @retval 接收到的一个字节数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,k,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
j = I2C_SDA;
k = (k<<1)|j;
//if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return k;
}
/**
* @brief I2C发送应答
* @param AckBit 应答位,0为应答,1为非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief I2C接收应答位
* @param 无
* @retval 接收到的应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
AT24C02.c
#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0//地址高四位固定是1010为a 后三位为000 写位为0
/**
* @brief AT24C02写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//发送器件地址写
I2C_ReceiveAck(); //收到应答
I2C_SendByte(WordAddress); //要写入字节的地址 自己随便定
I2C_ReceiveAck(); //收到应答
I2C_SendByte(Data); //发送(写入)数据
I2C_ReceiveAck(); //收到应答
I2C_Stop();
}
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//伪写
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);//代表要读数据
I2C_ReceiveAck();
Data=I2C_ReceiveByte(); //读到数据
I2C_SendAck(1);
I2C_Stop();
return Data;
}