0%

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);
}
}

DMA简介

•DMA(Direct Memory Access)直接存储器存取(DMA可以直接读取STM32内部存储器,包括运行内存SRAM、程序存储器Flash、寄存器等)
•DMA可以提供外设(外设的数据寄存器)和存储器(运行内容SRAM和程序存储器Flash)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
•12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
•每个通道都支持软件触发和特定的硬件触发(存储器到存储器的转运,一般使用软件触发;外设到存储器之间的数据转运,一般使用硬件触发)
•STM32F103C8T6 DMA资源:DMA1(7个通道)

存储器映像

类型起始地址存储器用途
ROM0x0800 0000程序存储器Flash存储C语言编译后的程序代码
0x1FFF F000系统存储器存储BootLoader,用于串口下载
0x1FFF F800选项字节存储一些独立于程序代码的配置参数
RAM0x2000 0000运行内存SRAM存储运行过程中的临时变量
0x4000 0000外设寄存器存储各个外设的配置参数
0xE000 0000内核外设寄存器存储内核各个外设的配置参数

DMA框图

DMA框图

上图,左上角魏Cortex-M3内核,包含CPU和内核外设。
剩下的所有东西,都可以看作是存储器。
Flash是主闪存,SRAM是运行内存,各个外设,都可以看作是寄存器,也是SRAM寄存器。

DMA基本结构

DMA基本结构
DMA的数据转运,可以从外设到存储器,也可以从存储器到外设,具体的方向,使用方向参数进行控制。
还有一直存储器到存储器的转运方式,比如Flash到SRAM或者SRAM到SRAM。由于Flash只读,因此不能进行Flash到Flash或者SRAM到Flash的操作。

传输计数器,用来指定要转运几次。是自减计数器。

自动重装器的作用:当传输计数器减到0之后,是否恢复到最初的值。

DMA的触发源:包括硬件触发和软件触发。具体选择哪个,由M2M这个参数决定。
软件触发:代表连续触发。不能和循环模式一起使用。
硬件触发:触发源可以选择ADC、串口、定时器等等,一般都是与外设有关的转运,这种转运一般需要特定的时机,比如ADC转换完成、串口收到数据、定时时间到等等。

DMA转运的几个条件:
1.开关控制,DMA_Cmd必须使能
2.传输计数器必须大于0
3.触发源,必须有触发信号。触发一次,转运一次,传输计数器自减一次,当传输计数器等于0,且没有自动重装时,这时无论是否触发,DMA都不在转运了。这时需要把DMA_Cmd给Disable,关闭DMA,再给传输计数器写大于0的数字,再DMA_Cmd给Enable,开启DMA。(这里注意:写传输计数器时,必须关闭DMA,再写)

DMA请求

DMA请求
上图是DMA1的请求映像,包括DMA1的7个通道。
每路都有一个选择器,可以选择硬件触发或者软件触发。
EN控制数据选择器是否工作。0不工作,1工作。
左侧是硬件触发源,是外设请求信号,可以看到每个信号的硬件出发源都是不同的。例如:需要ADC1触发,必须选择通道1。(硬件触发是这样,如果是软件触发,就任意选择了)

这7个触发源,进入仲裁器,进行优先级判断,产生最终的DMA1请求。
优先级判断,默认是通道号越小,优先级越高,也可在程序中自行设置。

数据宽度与对齐

数据宽度与对齐
如果外设和存储器的数据宽度一致,那就是正常的一个个转运。
如果数据宽度不一样,根据上表处理。

数据转运+DMA

数据转运+DMA
将SRAM里的数组A转运到另一个数组B中。

配置外设站点和存储器站点的起始地址、数据宽度、地址是否自增三个参数。
本例中:外设地址写数组A的首地址,存储器地址写数组B的首地址。
数据宽度,都设为8。
地址是否自增,由于需要DataA[0]转运到DataB[0]、DataA[1]转运到DataB[1]等,一一对应,因此都需要自增。

方向参数,设置为外设站点到存储器站点。(如果想从DataB到DataA,需要反过来)

需要转运7次,因此传输计数器给7,自动重装暂时不设置。

触发选择部分,选择软件触发。

最后,调用DMA_Cmd,给DMA使能。

ADC扫描模式+DMA

ADC扫描模式+DMA
左侧是ADC扫描模式的执行流程,在这里有7个通道,触发一次后,7个通道依次进行AD转换,转换结果都放在ADC_DR数据寄存器里面。
在每个单独的通道转换完成之后,应该进行DMA数据转运,且对目的地址进行自增,这样数据不会被覆盖。
因此,DMA配置为:
1.外设地址,写ADC_DR这个寄存器的地址。
2.存储器的地址,可以在SRAM中定义一个数组ADValue
3.数据宽度,16位
4.地址自增,外设地址不自增,存储器地址自增。
5.传输方向,外设站点到存储器站点
6.传输计数器,通道有7个,因此计数7次。
7.计数器是否自动重装,看ADC的配置,ADC如果是单次扫描,DMA的传输计数器可以不自动重装,转换一轮就停止。如果ADC是连续扫描的,那DMA可以使用自动重装,ADC启动下一轮时,DMA也启动下一轮转运,ADC和DMA同步工作。
8.触发选择,ADC硬件触发。

常用函数

1
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);

配置ADCCLK分频器,可以对APB2的72MHz选择2、4、6、8分频,输入到ADCCLK。

1
void ADC_DeInit(ADC_TypeDef* ADCx);

恢复缺省配置

1
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

初始化

1
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);

结构体初始化

1
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

ADC上电,开关控制。

1
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

开启DMA输出信号,如果使用DMA转运数据,调用此函数。

1
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);

中断输出控制,控制某个中断,通往NVIC。

1
2
3
4
void ADC_ResetCalibration(ADC_TypeDef* ADCx);//复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);//获取复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);//开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);//获取开始校准状态

在ADC初始化完成后,依次调用完成校准。

1
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

ADC软件开始转换控制,用于软件触发函数,软件触发转换

1
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

ADC获取软件开始转换状态,给SWSTART位置1,开始转换,返回SWSTART状态。(一般不用)

1
2
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

配置间断模式,第一个函数是每个几个通道间断一次,第二个函数是是否启用间断模式。

1
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

ADC规则组通道配置,给序列的每个位置填写指定的通道。

1
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

ADC外部触发转换控制,是否允许外部触发转换。

1
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

ADC获取转换值,获取AD转换数据寄存器,读取结果。

1
uint32_t ADC_GetDualModeConversionValue(void);

ADC获取双模式转换器,双ADC模式读取转换结果。

1
2
3
4
5
6
7
8
9
10
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);

对ADC注入组进行配置。

1
2
3
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);//是否启用模拟看门狗
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);//配置高低阈值
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);//配置看门通道

对模拟看门狗进行配置

1
void ADC_TempSensorVrefintCmd(FunctionalState NewState);

开启内部通道:ADC温度传感器、内部参考电压控制

1
2
3
4
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);//获取标志位状态
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);//清除标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);//获取中断状态
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);//清除中断挂起位

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
#include "stm32f10x.h"                  // Device header

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

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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC规则组通道配置,给序列的每个位置填写指定的通道。通道0。

//配置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 = DISABLE;//扫描转换模式,多通道还是单通道
ADC_InitStructure.ADC_NbrOfChannel = 1;//扫描模式下的通道数目,
ADC_Init(ADC1,&ADC_InitStructure);

ADC_Cmd(ADC1,ENABLE);

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

uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//获取标志位状态
return ADC_GetConversionValue(ADC1);//ADC获取转换值

}

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 "AD.h"
uint16_t ADValue;
int main(void)
{
OLED_Init();
AD_Init();

OLED_ShowString(1,1,"ADValue:");
OLED_ShowString(2,1,"Voltage:0.00V");
while(1)
{
ADValue = AD_GetValue();
OLED_ShowNum(1,9,ADValue,4);
OLED_ShowNum(2,9,((float)ADValue/4095*3.3),1);
OLED_ShowNum(2,11,((uint16_t)(((float)ADValue/4095*3.3)*100)%100),2);
Delay_ms(100);
}
}

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
#include "stm32f10x.h"                  // Device header

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

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);


//配置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 = DISABLE;//扫描转换模式,多通道还是单通道
ADC_InitStructure.ADC_NbrOfChannel = 1;//扫描模式下的通道数目,
ADC_Init(ADC1,&ADC_InitStructure);

ADC_Cmd(ADC1,ENABLE);

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

uint16_t AD_GetValue(uint8_t ADC_Channel)//传入AD通道
{
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//AD通道放在第二个参数,是传进来的。
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//获取标志位状态
return ADC_GetConversionValue(ADC1);//ADC获取转换值

}

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 "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)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);

OLED_ShowNum(1,5,AD0,4);
OLED_ShowNum(2,5,AD1,4);
OLED_ShowNum(3,5,AD2,4);
OLED_ShowNum(4,5,AD3,4);
Delay_ms(100);
}
}