0%

W25Q64简介

•W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器(数据掉电不丢失),常应用于数据存储、字库存储、固件程序存储等场景
•存储介质:Nor Flash(闪存)
•时钟频率:80MHz / 160MHz (Dual SPI双重SPI) / 320MHz (Quad SPI四重SPI)
•存储容量(24位地址):
  W25Q40:    4Mbit / 512KByte
  W25Q80:    8Mbit / 1MByte
  W25Q16:    16Mbit / 2MByte
  W25Q32:    32Mbit / 4MByte
  W25Q64:    64Mbit / 8MByte
  W25Q128:  128Mbit / 16MByte
  W25Q256:  256Mbit / 32MByte

硬件电路

硬件电路

硬件电路

引脚功能
VCC、GND电源(2.7~3.6V)
CS(SS)SPI片选
CLK(SCK)SPI时钟
DI(MOSI)SPI主机输出从机输入
DO(MISO)SPI主机输入从机输出
WP写保护
HOLD数据保持

W25Q64框图

W25Q64框图
右上角为存储器的规划示意图,以64KB为基本单元,分为若干个块。

Flash操作注意事项

写入操作时:

•写入操作前,必须先进行写使能(使用SPI发送写使能的指令)
•每个数据位只能由1改写为0,不能由0改写为1
•写入数据前必须先擦除,擦除后,所有数据位变为1
•擦除必须按最小擦除单元进行
•连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
•写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

•直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

软件SPI读写W25Q64

MySPI.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "stm32f10x.h"                  // Device header

void MySPI_W_CS(uint8_t BitValue)//写CS引脚
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

void MySPI_W_SCK(uint8_t BitValue)//写SCK引脚
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}

void MySPI_W_MOSI(uint8_t BitValue)//写MOSI引脚
{
GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}

uint8_t MySPI_R_MISO(void)//读MISO引脚
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初始化DI、CLK、CS为推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7);
//初始化DO为上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_6);
MySPI_W_CS(1);
MySPI_W_SCK(0);
}

void MySPI_Start(void)//起始信号,只需要把CS置低电平即可
{
MySPI_W_CS(0);
}

void MySPI_Stop(void)//终止信号,只需要把CS置高电平即可
{
MySPI_W_CS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)//交换字节
{
// uint8_t i,ByteReceive = 0x00;//接收到的字节
// for(i = 0; i < 8 ;i ++)
// {
// MySPI_W_MOSI(ByteSend & (0x80 >> i));
// MySPI_W_SCK(1);
// if (MySPI_R_MISO() == 1 )
// {
// ByteReceive |= (0x80 >> i);
// }
// MySPI_W_SCK(0);
// }
uint8_t i;
for(i = 0; i < 8 ;i ++)
{
MySPI_W_MOSI(ByteSend & 0x80);
ByteSend <<= 1;
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1 )
{
ByteSend |= 0x01;
}
MySPI_W_SCK(0);
}

return ByteSend;
}

W25Q64.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

void W25Q64_Init(void)//初始化W25Q64
{
MySPI_Init();
}

void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)//读取ID
{
MySPI_Start();//先发送起始信号
MySPI_SwapByte(W25Q64_JEDEC_ID);//发送0x9F,交换到厂商号
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收厂商号,发送的0xFF是为了交换到返回的厂商号
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收返回值的高8位
*DID <<= 8;//左移8位
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收返回值的低8位
MySPI_Stop();
}

void W25Q64_WriteEnable(void)//写使能
{
MySPI_Start();//先发送起始信号
MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送指令码,写使能
MySPI_Stop();//发送停止信号结束
}

void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start();//先发送起始信号
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//发送指令码,读状态寄存器1
Timeout = 100000;
while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
{
Timeout -- ;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count)
{
W25Q64_WriteEnable();
uint16_t i;
MySPI_Start();//先发送起始信号
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//发送指令码
MySPI_SwapByte(Address >> 16);//发送地址最高位
MySPI_SwapByte(Address >> 8);//发送地址中间位
MySPI_SwapByte(Address);//发送地址最低位,高位被舍弃了

for(i = 0; i < Count; i ++)
{
MySPI_SwapByte(DataArray[i]);
}

MySPI_Stop();
W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();//先发送起始信号
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//发送指令码,扇区擦除
MySPI_SwapByte(Address >> 16);//发送地址最高位
MySPI_SwapByte(Address >> 8);//发送地址中间位
MySPI_SwapByte(Address);//发送地址最低位,高位被舍弃了
MySPI_Stop();
W25Q64_WaitBusy();//事后等待
}

void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count)
{
uint32_t i;
MySPI_Start();//先发送起始信号
MySPI_SwapByte(W25Q64_READ_DATA);//发送指令码,读数据
MySPI_SwapByte(Address >> 16);//发送地址最高位
MySPI_SwapByte(Address >> 8);//发送地址中间位
MySPI_SwapByte(Address);//发送地址最低位,高位被舍弃了

for(i = 0; i < Count; i ++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//发送0xFF,换回有用的数据
}
MySPI_Stop();
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x55,0x66,0x77,0x88};
uint8_t ArrayRead[4];

int main(void)
{
OLED_Init();
W25Q64_Init();

OLED_ShowString(1,1,"MID: DID:");
OLED_ShowString(2,1,"W:");
OLED_ShowString(3,1,"R:");
W25Q64_ReadID(&MID,&DID);
OLED_ShowHexNum(1,5,MID,2);
OLED_ShowHexNum(1,12,DID,4);

// W25Q64_SectorErase(0x000000);
W25Q64_PageProgram(0x000000,ArrayWrite,4);
W25Q64_ReadData(0x000000,ArrayRead,4);

OLED_ShowHexNum(2,3,ArrayWrite[0],2);
OLED_ShowHexNum(2,6,ArrayWrite[1],2);
OLED_ShowHexNum(2,9,ArrayWrite[2],2);
OLED_ShowHexNum(2,12,ArrayWrite[3],2);

OLED_ShowHexNum(3,3,ArrayRead[0],2);
OLED_ShowHexNum(3,6,ArrayRead[1],2);
OLED_ShowHexNum(3,9,ArrayRead[2],2);
OLED_ShowHexNum(3,12,ArrayRead[3],2);
}


SPI通信

•SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
•四根通信线:SCK(Serial Clock串行时钟线)、MOSI(Master Output Slave Input主机输出从机输入)、MISO(Master Input Slave Output主机输入从机输出)、SS(Slave Select从机选择)
•同步,全双工(数据发送和接收各占一条线,互不影响)
支持总线挂载多设备(一主多从)

硬件电路

•所有SPI设备的SCK、MOSI、MISO分别连在一起
•主机另外引出多条SS控制线,分别接到各从机的SS引脚(SS线低电平有效
•输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
硬件电路
左侧的SPI主机,主导整个SPI总线,一般是控制器来做,比如STM32。

由于有3个从机,因此需要3根SS(从机选择)线。

上图中没画出GND线,但是需要所有设备共地。

时钟线SCK完全由主机掌控,因此对于主机来说SCK为输出,对于所有从机来说,SCK为输入,由此把主机的同步时钟送到各个从机。
MOSI,主机输出从机输入,主机通过MOSI输出,所有从机通过MOSI输入。
MISO,主机输入从机输出,所有从机通过MISO输出,主机通过MISO输入。

移位示意图

移位示意图
每来一个时钟,主机中的移位寄存器向左移位,从机也是一样。时钟上升沿,主机和从机进行输出,把数据放到MOSI和MISO线上,时钟下降沿,主机和从机进行读取,依次交换字节。

时钟源(波特率发生器)由主机提供,驱动主机的移位寄存器移位,同时通过SCK引脚输出,到从机的移位寄存器。

主机移位寄存器左侧移出的数据,通过MOSI引脚,输入到从机移位寄存器的右边。从机移位寄存器左边移出的数据,通过MISO引脚输入到主机移位寄存器的左侧。

如果SPI主机只想发送数据,不想接收怎么办?

按照交换字节的时序,发送同时接收,但是不读取接收到的内容即可。

SPI时序基本单元

•起始条件:SS从高电平切换到低电平,左侧,代表选中某个从机
•终止条件:SS从低电平切换到高电平,右侧,结束从机选中状态
SPI时序基本单元

!!! note “”
CPHA表示时钟相位,决定第一个时钟采样移入还是第二个时钟采样移入。

•交换一个字节(模式0)
•CPOL=0:空闲状态时,SCK为低电平
•CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
交换一个字节(模式0)

•交换一个字节(模式1)
•CPOL=0:空闲状态时,SCK为低电平
•CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
交换一个字节(模式1)

•交换一个字节(模式2)
•CPOL=1:空闲状态时,SCK为高电平
•CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
交换一个字节(模式2)

•交换一个字节(模式3)
•CPOL=1:空闲状态时,SCK为高电平
•CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
交换一个字节(模式3)

SPI时序

•发送指令
•向SS指定的设备,发送指令(0x06)
下图,主机用0x06换来了从机的0xFF,但是实际上从机没有输出,0xFF是默认的高电平,没有意义。
SPI时序

•指定地址写
•向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)
下图,发送的第一个数据0x02,为字节指令,第2(0x12)、3(0x34)、4(0x56)个字节为地址0x123456(W25Q64芯片的地址长度为3个字节),第5个字节为写入的数据,表示在0x123456地址下,写入0x55这个数据。
指定地址写

•指定地址读
•向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)
下图,发送的第一个数据0x03,为字节指令,第2(0x12)、3(0x34)、4(0x56)个字节为地址0x123456(W25Q64芯片的地址长度为3个字节),第5个字节为0xFF,是为了和从机交换给的数据,此时从机传回0x123456地址下的数据0x55。
指定地址读

I2C外设简介

•STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
•支持多主机模型
•支持7位(最常见,7位地址+读写位)/10位地址模式(为了挂载128个以上的设备)
•支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
•支持DMA(读写多字节时,使用DMA自动转运数据)
•兼容SMBus协议(I2C改进的协议)

•STM32F103C8T6 硬件I2C资源:I2C1、I2C2

I2C框图

I2C框图
左侧的SDA和SCL是I2C通信引脚,下方的SMBALERT是smbus使用的,I2C用不到。

上方是SDA,数据控制部分。
数据收发的核心,是数据寄存器(DATA REGISTER)和数据移位寄存器,需要发送数据时,把这个数据写道数据寄存器DR,当移位寄存器没有数据移位时,这个数据寄存器的值就会转到移位寄存器里,在移位过程中,可以把下一个数据放到数据寄存器DR中等待,前一个数据移位完成,下一个数据直接到移位寄存器。
当数据由数据寄存器转移到数据移位寄存器时,置状态寄存器的TXE位为1,表示发送寄存器为空。
接收数据时,数据一位一位地从引脚到移位寄存器中,一个字节的数据收集好之后,数据整体从移位寄存器转移到数据寄存器,置标志位RXNE,表示接收寄存器非空,此时可以从数据寄存器读出数据。

下方为SCL,时钟控制部分。

I2C基本结构

I2C基本结构
移位寄存器和数据寄存器DR配合,是通信的核心。
由于I2C是高位先行,移位寄存器是向左移位,从高位到低位移位。一个SCL时钟移位一次,八次把一个字节传输完成。
接收时,数据从GPIO口移进来,移位8次,一个字节就接收完成了。
使用硬件I2C时,对应的2个GPIO口配置为复用开漏输出模式

主机发送

主机发送

主机接收

主机接收

常用函数

1
2
void I2C_DeInit(I2C_TypeDef* I2Cx);

I2C恢复缺省配置

1
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);

I2C初始化

1
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);

I2C结构体初始化

1
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C使能

1
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C生成起始条件,调用此函数生成起始条件。

1
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C生成终止条件。

1
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

配置在收到一个字节之后,是否给从机应答

1
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);

I2C发送数据,写入数据到数据寄存器DR。

1
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

读取DR的数据作为返回值,接收数据

1
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);

发送7位地址

硬件I2C读写MPU6050

MPU6050.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "stm32f10x.h"                  // Device header
#include "MPU6050_REG.h"

#define MPU6050_ADDRESS 0xD0

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;//超时退出while,避免死循环
Timeout = 10000;
while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)//检测事件是否发送
{
Timeout--;
if(Timeout == 0)//超时退出
{
break;
}
}
}


void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//指定地址写
{

I2C_GenerateSTART(I2C2,ENABLE);//生成起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发送
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送从机地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件是否发送

I2C_SendData(I2C2,RegAddress);//发送寄存器地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//监测EV8事件是否发送

I2C_SendData(I2C2,Data);//发送数据
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件是否发送

I2C_GenerateSTOP(I2C2,ENABLE);//产生停止条件
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;

I2C_GenerateSTART(I2C2,ENABLE);//生成起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发送

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送从机地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件是否发送

I2C_SendData(I2C2,RegAddress);//发送寄存器地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8事件是否发送

I2C_GenerateSTART(I2C2,ENABLE);//生成重复的起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发送

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);//发送从机地址,读写位为读
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//监测EV6事件是否发送

I2C_AcknowledgeConfig(I2C2,DISABLE);//只需要读取一个字节,在EV6事件之后,立刻把ACK位置0
I2C_GenerateSTOP(I2C2,ENABLE);//申请产生终止条件

MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//监测EV7事件是否发送

Data = I2C_ReceiveData(I2C2);

I2C_AcknowledgeConfig(I2C2,ENABLE);//把ACK置1,默认状态下ACK应该为1

return Data;
}

void MPU6050_Init(void)
{
//MyI2C_Init();

//硬件I2C初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);//开启I2C2的时钟,I2C1和I2C2都是APB1的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB的时钟
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//此处必须设置为复用开漏模式,开漏是I2C协议的设计要求,复用是把GPIO的控制权交给硬件外设。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
//I2C初始化
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_16_9;//占空比,16_9就是低电平和高电平时间是16:9的关系
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//应答位配置
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//STM32作为从机,可以相应几位地址,这里配置成7位
I2C_InitStructure.I2C_OwnAddress1 = 0x00;//STM32作为从机时使用,自身地址
I2C_Init(I2C2,&I2C_InitStructure);

I2C_Cmd(I2C2,ENABLE);//I2C2使能

MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
MPU6050_WriteReg(MPU6050_CONFIG,0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH,DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}

MPU6050简介

•MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
•3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
•3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

MPU6050参数

•16位ADC采集传感器的模拟信号,量化范围:-32768~32767
•加速度计满量程选择:±2、±4、±8、±16(g)
•陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
•可配置的数字低通滤波器
•可配置的时钟源
•可配置的采样分频
•I2C从机地址:1101000(AD0=0)
1101001(AD0=1)

硬件电路

硬件电路

引脚功能
VCC、GND电源
SCL、SDAI2C通信引脚
XCL、XDA主机I2C通信引脚
AD0从机地址最低位
INT中断信号输出

MPU6050框图

MPU6050框图

软件I2C读写MPU6050

MyI2C.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void MyI2C_W_SCL(uint8_t BitValue)//调用此函数,参数给1或者0,可以释放或拉低SCL
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);//延时一段时间,避免CPU主频太快,从设备跟不上
}

void MyI2C_W_SDA(uint8_t BitValue)//调用此函数,参数给1或者0,可以释放或拉低SDA
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);//延时一段时间,避免CPU主频太快,从设备跟不上
}

uint8_t MyI2C_R_SDA(void)//读SDA
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);//延时一段时间,避免CPU主频太快,从设备跟不上
return BitValue;
}

void MyI2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//配置为开漏输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);//把PB10和11都置为高电平
}

void MyI2C_Start(void)//I2C起始
{
MyI2C_W_SDA(1);//先把SCL和SDA都释放,先释放SDA,再释放SCL,这个顺序是为了兼容重复起始
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);//起始条件:先拉低SDA,再拉低SCL
MyI2C_W_SCL(0);
}

void MyI2C_Stop(void)//I2C停止
{
MyI2C_W_SDA(0);//终止条件:先拉低SDA,再释放SCL,再释放SDA
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}

void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i = 0; i < 8; i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));//取出每一位,SDA根据传入的Byte修改电平
MyI2C_W_SCL(1);//释放SCL
MyI2C_W_SCL(0);//拉低SCL
}
}

uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;//先定义一个接收字节
MyI2C_W_SDA(1);//释放SDA
for(i = 0; i < 8; i++)
{
MyI2C_W_SCL(1);//释放SCL
if(MyI2C_R_SDA() == 1)
{
Byte |= (0x80>>i);
}
MyI2C_W_SCL(0);//拉低SCL
}
return Byte;
}

void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);//释放SDA
MyI2C_W_SCL(1);//释放SCL
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);//拉低SCL
return AckBit;
}

MPU6050.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_REG.h"

#define MPU6050_ADDRESS 0xD0

void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//指定地址写
{
MyI2C_Start();//I2C启动
MyI2C_SendByte(MPU6050_ADDRESS);//发送MPU6050的地址
MyI2C_ReceiveAck();//接收应答,下面还可以判断,此处省略
MyI2C_SendByte(RegAddress);//发送要写入的地址
MyI2C_ReceiveAck();//接收应答,下面还可以判断,此处省略
MyI2C_SendByte(Data);//发送要写入的数据
MyI2C_ReceiveAck();//接收应答,下面还可以判断,此处省略
MyI2C_Stop();//停止
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start();//I2C启动
MyI2C_SendByte(MPU6050_ADDRESS);//发送MPU6050的地址
MyI2C_ReceiveAck();//接收应答,下面还可以判断,此处省略
MyI2C_SendByte(RegAddress);//发送要读的地址
MyI2C_ReceiveAck();//接收应答,下面还可以判断,此处省略

MyI2C_Start();//重复启动
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//发送MPU6050的地址和读写位
MyI2C_ReceiveAck();//接收应答,下面还可以判断,此处省略
Data = MyI2C_ReceiveByte();//接收字节
MyI2C_SendAck(1);
MyI2C_Stop();//停止

return Data;
}

void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
MPU6050_WriteReg(MPU6050_CONFIG,0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH,DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY,AZ,GX,GY,GZ;

int main(void)
{
OLED_Init();
MPU6050_Init();
//MPU6050_WriteReg(0x6B,0x00);//取消睡眠模式
//MPU6050_WriteReg(0x19,0x66);//采样模式修改
//uint8_t ID = MPU6050_ReadReg(0x19);

//OLED_ShowHexNum(1,1,ID,2);

while(1)
{
MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
OLED_ShowSignedNum(2,1,AX,5);
OLED_ShowSignedNum(3,1,AY,5);
OLED_ShowSignedNum(4,1,AZ,5);
OLED_ShowSignedNum(2,8,GX,5);
OLED_ShowSignedNum(3,8,GY,5);
OLED_ShowSignedNum(4,8,GZ,5);
}
}

I2C通信

•I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
•两根通信线:SCL(Serial Clock)、SDA(Serial Data)
•同步,半双工
•带数据应答
•支持总线挂载多设备(一主多从、多主多从)

硬件电路

•所有I2C设备的SCL连在一起,SDA连在一起
•设备的SCL和SDA均要配置成开漏输出模式(否则可能短路)
•SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
硬件电路
上图为一主多从的模型,左侧CPU,即单片机,作为总线的主机。主机可以完全控制SCL线,在空闲状态下,也可以主动发起对SDA的控制,只有在从机发送数据或应答时,主机会把SDA的控制权移交从机。
下方为被控IC,即挂载在I2C总线上的从机。从机权力比较小,对于SCL时钟线,任何时刻只能被动读取。从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令之后,或者从机应答时,从机可以短暂获取SDA控制权。

I2C时序基本单元

•起始条件:SCL高电平期间,SDA从高电平切换到低电平
I2C时序基本单元
•终止条件:SCL高电平期间,SDA从低电平切换到高电平
I2C时序基本单元

•发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
发送一个字节

•接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
接收一个字节

•发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
•接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
应答

I2C时序

指定地址写

•对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
指定地址写

当前地址读

•对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
当前地址读

指定地址读

•对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
指定地址读

HEX数据包

HEX数据包
固定包长,含包头包尾:每个数据包的长度固定不变,前面是包头,后面是包尾
可变包头,含包头包尾:每个数据包的长度可以不一样,前面是包头,后面是包尾

固定包头这里规定,每组4个字节,在这4个字节之前,加上定义的包头0xFF,后面加上定义的包尾0xFE

另外,还可以不要包尾,只使用一个包头FF,接收到包头FF后,开始接收,收够4个字节之后,置标志位,一个数据包接收完成。

如果传输的数据本身就是FF和FE怎么办呢?

限制载荷数据的范围,在发送时进行限幅;
或者使用固定长度的数据包,可以通过包头包尾对其数据,哪个数据是包头,哪个数据是包尾,哪个数据是载荷数据,在接收载荷数据时,不会判断这个数据是否为包头或包尾;
或者增加包头包尾的数量,让它尽量呈现出载荷数据出现不了的状态

如果载荷不会和包头包尾重复,可以选择可变包长

数据长度可以任意改变,因为包头和包尾都是唯一的,出现包头,就接收,出现包尾,就停止接收。

文本数据包

文本数据包
在文本数据包中,数据经过了一次编码和译码,表现出来的文本格式。
由于数据译码成了字符形式,就可以有大量的字符作为包头和包尾,可以有效避免载荷和包头包尾重复的问题。

HEX数据包接收

HEX数据包接收
对于数据包来说,接收具有前后的关联性,包头->数据->包尾,对于这三种状态,需要不同的处理逻辑,即状态机
对于接收数据,可以定义为三个状态。等待包头,然后接收数据,最后等待包尾,每个状态用一个变量来标志一下。

文本数据包接收

文本数据包接收

串口收发HEX数据包

Serial.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];//发数组
uint8_t Serial_RxPacket[4];//收数组
uint8_t Serial_RxFlag;//收到标志位



void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//引脚模式,TX是USART外设控制的输出脚,选择复用推挽输出;RX引脚是USART外设控制的输入脚,选择输入模式,一般RX配置为浮空输入或者上拉输入。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//RX配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//发送模式
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,选择不需要校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长,由于前面没有选择校验,这里选8位
USART_Init(USART1,&USART_InitStructure);

USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC的输出
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

USART_Cmd(USART1,ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );//获取标志位,发送寄存器空标志位,避免产生数据覆盖的问题

}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i=0;i<Length;i++)
{
Serial_SendByte(Array[i]);
}
}

void Serial_SendString(char *String)
{
uint8_t i;
for ( i = 0; String[i] != '\0'; i++ )
{
Serial_SendByte(String[i]);
}
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}


void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for ( i = 0; i<Length; i++ )
{
Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) % 10 + '0');
}
}

int fputc(int ch, FILE *f)//printf 输出到串口
{
Serial_SendByte(ch);
return ch;
}

void Serial_Printf(char *format, ... )
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}

void Serial_SendPacket(void)//发送数据包
{
Serial_SendByte(0xFF);//发送包头
Serial_SendArray(Serial_TxPacket,4);//发送数据包内容
Serial_SendByte(0xFE);//发送包尾
}

uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}

void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;
static uint8_t PRxPacket = 0;
if(USART_GetFlagStatus(USART1,USART_IT_RXNE) == SET)
{
//三种状态
uint8_t RxData = USART_ReceiveData(USART1);//获取当前接收到的数据
if(RxState == 0)
{
if(RxData == 0xFF)//接收到包头
{
RxState = 1;//进入下一步,开始接收有效载荷
}
}
else if(RxState == 1)
{
Serial_RxPacket[PRxPacket] = RxData;//接收一个数据
PRxPacket++;//存的位置自增
if(PRxPacket >= 4)//4组数据全部记录完
{
RxState = 2;//进入下一步,等待包尾
PRxPacket = 0;//记录的位置清0,为下一次准备
}
}
else if(RxState == 2)
{
if(RxData == 0xFE)//接收到包尾
{
RxState = 0;//回到最初状态,一个数据包接收完成
Serial_RxFlag = 1;//接收完成标志位置1
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t KeyNum;

int main(void)
{
OLED_Init();
KEY_Init();
Serial_Init();

OLED_ShowString(1,1,"TxPacket:");
OLED_ShowString(3,1,"RxPacket:");
//给数组一个初值
Serial_TxPacket[0] = 0x01;
Serial_TxPacket[1] = 0x02;
Serial_TxPacket[2] = 0x03;
Serial_TxPacket[3] = 0x04;

while (1)
{

KeyNum = Key_GetNum();
if (KeyNum == 1)//按钮按下
{
Serial_TxPacket[0] ++;//发数组的值自增1
Serial_TxPacket[1] ++;
Serial_TxPacket[2] ++;
Serial_TxPacket[3] ++;
Serial_SendPacket();//发送数组
OLED_ShowHexNum(2,1,Serial_TxPacket[0],2);//显示发送的数组
OLED_ShowHexNum(2,4,Serial_TxPacket[1],2);
OLED_ShowHexNum(2,7,Serial_TxPacket[2],2);
OLED_ShowHexNum(2,10,Serial_TxPacket[3],2);
}
if (Serial_GetRxFlag() == 1)//收到数组
{
OLED_ShowHexNum(4,1,Serial_RxPacket[0],2);//显示收到的数组
OLED_ShowHexNum(4,4,Serial_RxPacket[1],2);
OLED_ShowHexNum(4,7,Serial_RxPacket[2],2);
OLED_ShowHexNum(4,10,Serial_RxPacket[3],2);
}
}
}

串口收发文本数据包

Serial.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

char Serial_RxPacket[100];
uint8_t Serial_RxFlag;



void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//引脚模式,TX是USART外设控制的输出脚,选择复用推挽输出;RX引脚是USART外设控制的输入脚,选择输入模式,一般RX配置为浮空输入或者上拉输入。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//RX配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//发送模式
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,选择不需要校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长,由于前面没有选择校验,这里选8位
USART_Init(USART1,&USART_InitStructure);

USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC的输出
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

USART_Cmd(USART1,ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );//获取标志位,发送寄存器空标志位,避免产生数据覆盖的问题

}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i=0;i<Length;i++)
{
Serial_SendByte(Array[i]);
}
}

void Serial_SendString(char *String)
{
uint8_t i;
for ( i = 0; String[i] != '\0'; i++ )
{
Serial_SendByte(String[i]);
}
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}


void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for ( i = 0; i<Length; i++ )
{
Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) % 10 + '0');
}
}

int fputc(int ch, FILE *f)//printf 输出到串口
{
Serial_SendByte(ch);
return ch;
}

void Serial_Printf(char *format, ... )
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}




void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;
static uint8_t PRxPacket = 0;
if(USART_GetFlagStatus(USART1,USART_IT_RXNE) == SET)
{
//三种状态
uint8_t RxData = USART_ReceiveData(USART1);//获取当前接收到的数据
if(RxState == 0)
{
if(RxData == '@' && Serial_RxFlag == 0)//接收到包头
{
RxState = 1;//进入下一步,开始接收有效载荷
PRxPacket = 0;//计数器清0
}
}
else if(RxState == 1)
{
if(RxData == '\r')//先判断接收到的是不是包尾
{
RxState = 2;
}
else//不是包尾,记录数据
{
Serial_RxPacket[PRxPacket] = RxData;//接收一个数据
PRxPacket++;//存的位置自增
}
}
else if(RxState == 2)
{
if(RxData == '\n')//接收到包尾
{
RxState = 0;//回到最初状态,一个数据包接收完成
Serial_RxPacket[PRxPacket] = '\0';
Serial_RxFlag = 1;//接收完成标志位置1
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h>

int main(void)
{
OLED_Init();
LED_Init();
Serial_Init();

OLED_ShowString(1,1,"TxPacket:");
OLED_ShowString(3,1,"RxPacket:");



while (1)
{
if(Serial_RxFlag==1)
{
OLED_ShowString(4,1," ");
OLED_ShowString(4,1,Serial_RxPacket);

if(strcmp(Serial_RxPacket,"LED_ON") == 0)
{
LED1_ON();
Serial_SendString("LED_ON_OK");
OLED_ShowString(4,1," ");
OLED_ShowString(4,1,"LED_ON_OK");
}
else if(strcmp(Serial_RxPacket,"LED_OFF") == 0)
{
LED1_OFF();
Serial_SendString("LED_OFF_OK");
OLED_ShowString(4,1," ");
OLED_ShowString(4,1,"LED_OFF_OK");
}
else
{
Serial_SendString("ERROR");
OLED_ShowString(4,1," ");
OLED_ShowString(4,1,"ERROR");
}
Serial_RxFlag = 0;
}
}
}


常用函数

1
void USART_DeInit(USART_TypeDef* USARTx);

USART恢复缺省配置

1
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

USART初始化结构体

1
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

USART结构体初始化

1
2
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);

配置同步时钟输出,时钟是否输出、时钟的极性、相位等参数,使用结构体配置。

1
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

USART开启指令

1
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

USART中断配置

1
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

开启USART到DMA的触发通道

1
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

发送数据,写DR寄存器

1
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

接收数据,读DR寄存器

串口发送

Serial.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//引脚模式,TX是USART外设控制的输出脚,选择复用推挽输出;RX引脚是USART外设控制的输入脚,选择输入模式,一般RX配置为浮空输入或者上拉输入。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//PA9是USART1的TX复用端口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx;//发送模式
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,选择不需要校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长,由于前面没有选择校验,这里选8位
USART_Init(USART1,&USART_InitStructure);

USART_Cmd(USART1,ENABLE);//USART使能
}

void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);//USART发送数据
while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );//获取标志位,发送寄存器空标志位,避免产生数据覆盖的问题
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)//USART发送数组
{
uint16_t i;
for (i=0;i<Length;i++)
{
Serial_SendByte(Array[i]);
}
}

void Serial_SendString(char *String)//USART发送字符串
{
uint8_t i;
for ( i = 0; String[i] != '\0'; i++ )
{
Serial_SendByte(String[i]);
}
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}


void Serial_SendNumber(uint32_t Number, uint8_t Length)//USART发送数字
{
uint8_t i;
for ( i = 0; i<Length; i++ )
{
Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) % 10 + '0');
}
}

int fputc(int ch, FILE *f)//printf 输出到串口
{
Serial_SendByte(ch);
return ch;
}

void Serial_Printf(char *format, ... )
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main(void)
{
OLED_Init();
Serial_Init();

// Serial_SendByte('A');

// uint8_t MyArray[] = {0x41,0x42,0x43,0x44};
// Serial_SendArray(MyArray, 4);

// Serial_SendString("Hello World!");
// Serial_SendNumber(12345, 5);
// printf("Num = %d\r\n",666);

// char String[100];
// sprintf(String,"Num = %d\r\n",666);
// Serial_SendString(String);

Serial_Printf("Num = %d\r\n",666);
}

串口发送+接收

Serial.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;



void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟

GPIO_InitTypeDef GPIO_InitStructure;
//配置PA9为TX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//引脚模式,TX是USART外设控制的输出脚,选择复用推挽输出;RX引脚是USART外设控制的输入脚,选择输入模式,一般RX配置为浮空输入或者上拉输入。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置PA10为RX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//RX配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//USART的RX和TX模式打开
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,选择不需要校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长,由于前面没有选择校验,这里选8位
USART_Init(USART1,&USART_InitStructure);

USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC的输出
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

USART_Cmd(USART1,ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );//获取标志位,发送寄存器空标志位,避免产生数据覆盖的问题

}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i=0;i<Length;i++)
{
Serial_SendByte(Array[i]);
}
}

void Serial_SendString(char *String)
{
uint8_t i;
for ( i = 0; String[i] != '\0'; i++ )
{
Serial_SendByte(String[i]);
}
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}


void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for ( i = 0; i<Length; i++ )
{
Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) % 10 + '0');
}
}

int fputc(int ch, FILE *f)//printf 输出到串口
{
Serial_SendByte(ch);
return ch;
}

void Serial_Printf(char *format, ... )
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}

uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}

void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1,USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData;
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"RxData:");
Serial_Init();
while (1)
{
if(Serial_GetRxFlag() == 1)
{
RxData = Serial_GetRxData();
Serial_SendByte(RxData);
OLED_ShowHexNum(1,8,RxData,2);

}
}
}

USART简介

•USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
•USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
•自带波特率发生器,最高达4.5Mbits/s
•可配置数据位长度(8/9),包含奇偶校验位,一般不需要校验位选8位;需要校验位选9位。
•停止位长度(0.5/1/1.5/2),决定帧的间隔,一般是1位停止位。
•可选校验位(无校验/奇校验/偶校验),通常用无校验
•支持同步模式(多了时钟CLK的输出)、硬件流控制(准备信号,防止数据丢失)、DMA(串口可以使用DMA转运数据,减轻CPU负担)、智能卡、IrDA、LIN

•STM32F103C8T6 USART资源: USART1(APB2)、 USART2(APB1)、 USART3(APB1)

USART框图

USART框图
左上角的TX和RX,就是发送和接收的引脚。
发送数据寄存器(TDR)和接收数据寄存器(RDR)这2个寄存器占用同一个地址,在程序上,只表现为一个寄存器,就是数据寄存器(DR),在实际硬件中,分成2个寄存器,一个用于发送(TDR),一个用于接收(RDR),TDR只写,RDR只读。
进行写操作时,数据写入到TDR。进行读操作时,数据从RDR读出。
发送移位寄存器,把一个字节的数据一位一位地移出,正好对应串口协议的波形数据位。

例如,某刻给TDR写入了数据0x55,在寄存器中就是二进制数0101 0101,硬件检测到写入数据,会检查当前移位寄存器是否有数据在移位,如果没有,0101 0101会立刻全部移动到发送移位寄存器,准备发送,数据从TDR移动到发送移位寄存器时,会置TXR标志位(发送寄存器空),我们检查到这个标志位置1了,就可以在TDR写入下一个数据,注意当TXR置1时,数据并没有发送出去。发送移位寄存器会在发送器控制的驱动下,向右移位,然后一位一位地,把数据输出到TX引脚,向右移位正好和串口规定的低位先行一致,数据移动完成后,新数据再次从TDR移动到发送移位寄存器。

如果当前移位寄存器移位没有完成,TDR数据会进行等待,移位完成后,会立刻转移过来。

接收数据过程与发送类似。

发送器控制,用来控制发送移位寄存器工作;接收器控制,用来控制接收移位寄存器工作。
硬件数据流控(简称流控),nRTS是请求发送,是输出脚。
nCTS是清除发送,是输入脚,用于接受nRTS的信号。
CTS和RTS要交叉连接,用于判断是否可以发送数据。

唤醒单元可以用来实现多设备的功能,可以给串口分配一个USART地址,发送指定地址时,唤醒的设备开始工作。

USART基本结构

USART基本结构
波特率发生器,用于产生约定的通信速率,时钟来源是PCLK2或1。

数据帧

数据帧
最好选择9位字长,有校验。或者8位字长,无校验。这样每一帧的有效载荷都是1字节。
数据帧2

起始位侦测

起始位侦测
输入电路侦测到一个数据帧的起始位之后,就会以波特率的形式,连续采样一帧的数据,同时,从起始位开始,采样位置要对齐到正中间,只要第一位是对齐的,后面肯定都是对齐的。

输入部分电路对采样时钟进行细分,以波特率的16倍频率进行采样。

数据采样

数据采样

波特率发生器

•发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
•计算公式:波特率 = fPCLK2/1 / (16 * DIV)
波特率发生器

通信接口

•通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
•通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

名称引脚双工时钟电平设备
USARTTX(数据发送脚)、RX(数据接收脚)全双工异步单端点对点
I2CSCL(时钟)、SDA(数据)半双工同步单端多设备
SPISCLK(时钟)、MOSI(主机输出数据脚)、MISO(主机输入数据脚)、CS(片选:指定通信对象)全双工同步单端多设备
CANCAN_H、CAN_L(差分数据脚,2个脚共同表示一个差分数据)半双工异步差分多设备
USBDP、DM(差分数据脚)半双工异步差分点对点
全双工:通信双方可以同时进行双向通信,一般来说,全双工都有2根通信线
半双工:可以双向通信,但在一个时刻,只能一方在发送,另一方在接受,一般来说,半双工有一根数据线,(CAN和USB的差分线是组合的一根数据线)
单工:只能由一个设备到另一个设备

I2C和SPI有单独的时钟线,所以它们是同步的,接收方可以在时钟信号的指引下采样。
其他的没有时钟线,需要双方约定一个采样频率,还需要加上帧头帧尾进行对齐,也就是异步通信

单端信号的引脚高低电平都是对地的电压差,因此单端信号通信双方必须共地。
差分信号:是靠2个差分引脚的电压差来传输信号,可以不需要GND,差分信号传输速度和距离都很高,性能好

串口通信

•串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
•单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

硬件电路

简单双向串口通信有两根通信线(发送端TX和接收端RX)
TX与RX要交叉连接
当只需单向的数据传输时,可以只接一根通信线
当电平标准不一致时,需要加电平转换芯片

硬件电路

电平标准

•电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
•TTL电平:+3.3V或+5V表示1,0V表示0
•RS232电平:-3-15V表示1,+3+15V表示0
•RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)

串口参数及时序

•波特率:串口通信的速率(每秒传输码元的速率,在二进制下,等于比特率,单位是bit/s)
•起始位:标志一个数据帧的开始,固定为低电平(固定为低电平是因为要打破空闲状态的高电平)
•数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
•校验位:用于数据验证,根据数据位计算得来
•停止位:用于数据帧间隔,固定为高电平(为下一个起始位做准备)
串口参数及时序
数据位,低位先行。
例如要发送一个数据0x0F,二进制就是0000 1111
要从低位先反过来发送,即先发送1111,再发送0000

校验位包括奇校验和偶校验。
奇校验:包括校验位在内,会出现奇数个1
偶校验:包括校验位在哪,出现偶数个1

例如,发送0000 1111,总共是4个1,4个0。
目前是4个1,是偶数个,如果使用奇校验,校验位是1,就是0000 1111 1,算上校验位是5个1,是奇数。

奇偶校验的检出率比较低,比如有2位数据同时出错,奇偶特性不变,就校验不出了。更高检出率,需要使用CRC校验。

1
uint8_t aa = 0x55;

变量保存在SRAM中。

1
const uint8_t bb = 0x66;

在STM32中,使用const定义的常量,存储在Flash中,如果有一个很大的查找表或者字库之类的,最好加const,保存在Flash中,避免占用SRAM。

1
OLED_ShowHexNum(1,1,(uint32_t)&ADC1->DR,8);

外设寄存器的地址,是固定的,在手册中可以查询到。

常用函数

1
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);

DMA恢复缺省配置

1
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

DMA初始化

1
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);

DMA结构体初始化

1
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

DMA使能

1
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

DMA中断输出使能

1
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

DMA设置当前数据寄存器,给传输计数器写数据

1
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

DMA获取当前数据寄存器,返回传输计数器的值,可以查看还有多少数据没有转运。

1
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

获取标志位状态

1
void DMA_ClearFlag(uint32_t DMAy_FLAG);

清除标志位

1
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);

获取中断状态

1
void DMA_ClearITPendingBit(uint32_t DMAy_IT);

清除中断挂起位

8-1DMA数据转运

MyDMA.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "stm32f10x.h"                  // Device header
uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t size)
{
MyDMA_Size = size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备

DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设站点的起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设站点的数据宽度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//外设站点是否自增
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//存储器站点的起始地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器站点的数据宽度
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器站点是否自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向
DMA_InitStructure.DMA_BufferSize = size;//缓冲区大小(传输计数器)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式(是否自动重装)
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//是否是存储器到存储器(软件触发还是硬件触发)
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);

DMA_Cmd(DMA1_Channel1,DISABLE);
}

void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1,DISABLE);//失能DMA
DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//设置传输计数器
DMA_Cmd(DMA1_Channel1,ENABLE);//再次使能DMA

while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//查看转运完成标志位
DMA_ClearFlag(DMA1_FLAG_TC1);
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};//源端数组
uint8_t DataB[] = {0, 0, 0, 0};//目标数组

int main(void)
{
OLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);//把2个数组传进去,长度为4

//先展示两个数组的地址
OLED_ShowString(1,1,"DataA");
OLED_ShowString(3,1,"DataB");
OLED_ShowHexNum(1,8,(uint32_t)DataA,8);
OLED_ShowHexNum(3,8,(uint32_t)DataB,8);

while(1)
{
//给DataA数组数据自增1
DataA[0]++;
DataA[1]++;
DataA[2]++;
DataA[3]++;
//展示2个数组的值
OLED_ShowHexNum(2,1,DataA[0],2);
OLED_ShowHexNum(2,4,DataA[1],2);
OLED_ShowHexNum(2,7,DataA[2],2);
OLED_ShowHexNum(2,10,DataA[3],2);
OLED_ShowHexNum(4,1,DataB[0],2);
OLED_ShowHexNum(4,4,DataB[1],2);
OLED_ShowHexNum(4,7,DataB[2],2);
OLED_ShowHexNum(4,10,DataB[3],2);
//延时1s,触发DMA转运
Delay_s(1);
MyDMA_Transfer();
//展示转运之后的2个数组的值
OLED_ShowHexNum(2,1,DataA[0],2);
OLED_ShowHexNum(2,4,DataA[1],2);
OLED_ShowHexNum(2,7,DataA[2],2);
OLED_ShowHexNum(2,10,DataA[3],2);
OLED_ShowHexNum(4,1,DataB[0],2);
OLED_ShowHexNum(4,4,DataB[1],2);
OLED_ShowHexNum(4,7,DataB[2],2);
OLED_ShowHexNum(4,10,DataB[3],2);

Delay_s(1);
}

}

DMA+AD多通道

AD.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include "stm32f10x.h"                  // Device header

uint16_t ADValue[4];

void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟,ADC是APB2上的设备
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//开启DMA时钟

RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK,分频时钟

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//选择模拟输入模式,是ADC的专属模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

//扫描PA0-PA3一共四个通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//通道0
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);//通道1
ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);//通道2
ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);//通道3

//配置ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发,使用软件触发
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC工作模式,独立模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//连续转换模式还是单次转换模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式写ENABLE
ADC_InitStructure.ADC_NbrOfChannel = 4;//扫描模式下的通道数目,4个通道
ADC_Init(ADC1,&ADC_InitStructure);



//ADC扫描模式+DMA数据转运
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设基地址,从这里开始,ADC1->DR就是外设基地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//想要DR寄存器低16位的寄存器,因此数据宽度写半字
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//不自增,始终转运一个地址的数据
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADValue;//存储器基地址,保存在数组中
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//也是半字
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器地址要自增,每转运一次,到下一个
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向
DMA_InitStructure.DMA_BufferSize = 4;//有4个通道,传输4次
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//不使用软件触发,使用硬件触发,触发源为ADC1
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel1,&DMA_InitStructure);//这里通道不能任意选择

DMA_Cmd(DMA1_Channel1,DISABLE);

ADC_DMACmd(ADC1,ENABLE);//使能ADC和DMA通道

ADC_Cmd(ADC1,ENABLE);

//对ADC进行校准
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回复位校准状态
ADC_StartCalibration(ADC1);//开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
}

void AD_GetValue(void)
{
DMA_Cmd(DMA1_Channel1,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1,4);
DMA_Cmd(DMA1_Channel1,ENABLE);

ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换

while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待转运完成
DMA_ClearFlag(DMA1_FLAG_TC1);//恢复标志位
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3;
int main(void)
{
OLED_Init();
AD_Init();

OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while(1)
{
AD_GetValue();

OLED_ShowNum(1,5,ADValue[0],4);
OLED_ShowNum(2,5,ADValue[1],4);
OLED_ShowNum(3,5,ADValue[2],4);
OLED_ShowNum(4,5,ADValue[3],4);
Delay_ms(100);
}
}