RCC: reset and clock control 复位和时钟控制

配置系统时钟,开启/关闭各个外设的时钟电源。

一般首要操作就是通过RCC去打开相应外设的时钟,比如:

1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);

通过RCC打开APB2总线外设的GPIOC的时钟,使他开启(ENABLE)。

GPIO_InitTypeDef 结构体

对GPIO进行设置/初始化之前,要先定义一个结构体,这个模板里规定了:如果你想配置 GPIO,你必须提供 模式速度引脚号 这三样东西。

1
2
3
4
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStryct.GPIO_Speed=GPIO_Speed_50MHz;

然后再去对GPIO进行配置

1
GPIO_InitStryct.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_SetBits/GPIO_ResetBits

1
2
3
4
GPIO_SetBits(GPIO_TypeDef* GPIOX , GPIO_Pin);
// 将某个GPIO引脚输出为高电平。
GPIO_ResetBits(GPIO_TypeDef* GPIOX , GPIO_Pin);
// 将某个GPIO引脚输出为低电平。

GPIO_WriteBits

1
2
3
GPIO_WriteBits(GPIO_TypeDef* GPIOX , GPIO_Pin , BitVal);
// 其中BitVal是枚举类型可以设置为Bit_Reset(低电平)=0和Bit_Set(高电平)=1。
// 将某个GPIO引脚输出为高/低电平。

同时初始化多个GPIO要用或(|)而不是与(&)

1
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1 | GPIO_Pin_2;

因为:

1
2
3
4
5
  0000 0000 0000 0001  (Pin_0)
| 0000 0000 0000 0010 (Pin_1)
| 0000 0000 0000 0100 (Pin_2)
-----------------------
= 0000 0000 0000 0111 (结果是 0x0007)

这是计算过程,如果是与的话,就是:

1
2
3
4
5
  0000 0000 0000 0001  (Pin_0)
& 0000 0000 0000 0010 (Pin_1)
& 0000 0000 0000 0100 (Pin_2)
-----------------------
= 0000 0000 0000 0000 (结果是 0x0000)

因为这三个数的 1 都在不同的位置上,没有任何重叠,所以按位与的结果是 全 0,相当于啥也没干。

推挽输出/开漏输出驱动GPIO

推挽输出:

1
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;

这使得GPIO输出高低电平都有驱动能力。

开漏输出:

1
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;

这使得GPIO输出高电平时没有驱动能力,

输出低电平时才有驱动能力。

上拉输入

1
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;

上拉输入模式,默认保持高电平。

接一个开关按钮,一端接GND,一端接GPIO端口。

输入寄存器默认高电平,也就是默认1(因为是上拉输入模式),如果通过GPIO读取函数读到寄存器的某一位为0,意味着按钮接通了,按钮的一端GND会导致GPIO端口被输入强下拉信号,也就是低电平,因此当检测到输入寄存器的电平为0时,说明按钮被按下。

1
2
3
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){
//说明按键触发。
}

按键封装函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void key_Init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50Hz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}

uint8_t Key_GetNum(void){
uint8_t KeyNum=0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){
Delay(30);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){}
Delay(30);
KeyNum=1;
}
return KeyNum;
}

GPIO初始化固定模版

1
2
3
4
5
6
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO读取函数

GPIO作为输入引脚

1
2
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)
// 读取GPIO作为输入引脚,外设给他的电平值,此函数输出为1或0,为uint8_t类型,只有0或者1。

示例应用(eg:通过读取光敏电阻输入的高低电平来控制蜂鸣器)

1
2
3
4
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)==1) {
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
//通过读取光敏电阻输入的高低电平来控制蜂鸣器。

GPIO作为输出引脚

1
2
GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_2)
//读取GPIO作为输出引脚,此时的电平值,相当于,我在前面给他设好了电平值,我现在读取一下,把这个uint8_t类型的1/0的高低电平值用来进行其他操作。

示例应用(eg:摁钮控制LED灯,翻转他的电平,让他亮或灭)

1
2
3
4
5
6
7
8
9
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)==0) {
Delay(50);
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)==0){}
if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_2)==1)
GPIO_ResetBits(GPIOB,GPIO_Pin_2);
else{
GPIO_SetBits(GPIOB,GPIO_Pin_2);
}
}

中断配置

GPIO——AFIO——EXTI——NVIC

模块化编程,单开一个文件,简历库函数和中断函数。

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
void countsenser_init() {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入,默认高电平。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //A3引脚
GPIO_StructInit(&GPIO_InitStructure);
//GPIO初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//AFIO初始化
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);//A3作为外部中断。
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发中断。
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
//EXTI初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置优先级。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//NVIC初始化


void EXTI3_IRQHandler() {
if (EXTI_GetITStatus(EXTI_Line3) != RESET) //检查标志位是不是被触发,标志位函数默认为0,触发为1。
{
//自由发挥
}
EXTI_ClearITPendingBit(EXTI_Line3); //!!必须写上这个函数,每一次中断结束后都要清除标志位。
}

定时器配置中断

按照上图顺序从左到右依次配置。

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
void Timer_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//使能RCC内部时钟TIM2
TIM_InternalClockConfig(TIM2);
//TIM2设置内部时钟模式,这行可以不写,因为默认就是内部时钟模式


TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //这里用不到,默认DIV1。
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式:向上计数。
TIM_TimeBaseStructure.TIM_Period = 10000-1; //计数10000就触发中断。
TIM_TimeBaseStructure.TIM_Prescaler = 7200-1;
//72MHz/7200=72,000,000Hz/7200=10000,把时钟频率从72MHz降到10000Hz,也就是每秒跳动10000次。
//TIM_Period和TIM_Prescaler组合,实现了1s触发中断一次。
TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //重复计数器,高级定时器才需要,一会把他注释掉看看能不能用。
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//配置时基单元
//--------------------------------------------------------------------------//


//下面是使能中断(IT)部分,不用中断就不用设置
TIM_ClearFlag(TIM2, TIM_IT_Update);
//STM32系统默认配置后后会触发一次中断,所以我们要在使能TIM2之前先清除标志位。
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //定时器2使能中断,向上计数。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
//配置通道,这里和GPIO那边的通道做区分,GPIO那边是EXTI9_5_IRQn这种。
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//NVIC配置
//--------------------------------------------------------------------------//


TIM_Cmd(TIM2, ENABLE);
//开启TIM2,前面都是配置默认使能等等,这里是给他明确指令,开始干活。
}

//下面是中断函数
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
//xxxxxxxxxxxxxxx
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}

上方是使用内部时钟模式

下方是使用ETR外部触发输入来计数。

也就是把:

TIM_InternalClockConfig(TIM2);

换成:

TIM_ETRClockMode2Config();

具体如下:

1
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x03);

四个参数:

TIM2:选择定时器2

TIM_ExtTRGPSC_OFF:不分频,外部触发一次,计数一次。

TIM_ExtTRGPolarity_NonInverted:上升沿或高电平触发。

0x03:信号滤波,一般设0x03就行。

1
2
TIM_TimeBaseStructure.TIM_Period = 10-1;
TIM_TimeBaseStructure.TIM_Prescaler = 1-1;

TIM_Period = 10-1,意味着计数器记到10就触发中断。

TIM_Prescaler = 1-1,意味着外部触发信号来一次,计数器就记一次。而上一个代码用内部时钟计数时,为啥要写7200-1,是因为内部时钟触发信号相当于一秒来72,000,000次,所以你必须先分频7200次,相当于每秒来10000次,而同时TIM_Period设置为10000-1,即计数器加到10000才触发一次中断,这样就实现了一秒触发一次中断。

PWM实现呼吸灯

PWM主要靠定时器,不需要中断,所以不用配置NVIC,EXTI等等,需要配置定时器和GPIO。

PWM初始化函数

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
void PWM_Init() {		
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//开启TIM2时钟


TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 100-1; //ARR
TIM_TimeBaseStructure.TIM_Prescaler = 720-1; //PSC
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//时基单元初始化


TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
//先把TIM_OCInitStructure结构体中的参数都初始化默认,防止出错,下面把需要修改的参数单独修改
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//一般用PWM就默认这个模式就可以了:TIM_OCMode_PWM1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//设置极性,一般用High
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//使能,不必多说
TIM_OCInitStructure.TIM_Pulse= 0; //CCR 由于上面ARR与PSC的赋值,这里的CCR在0-100代表PWM的占空比
//设置CCR比较值
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
TIM_OC3Init(TIM2,&TIM_OCInitStructure);
TIM_OC4Init(TIM2,&TIM_OCInitStructure);
//输出比较单元初始化,看你的GPIO对应的是哪个通道。


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化GPIO口

TIM_Cmd(TIM2, ENABLE);//最后一步,开启TIM2
}

void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
//把写入CCR的值封装成一个可读性高的函数。

void PWM_SetPSC(uint16_t PSC) {
TIM_PrescalerConfig(TIM2,PSC,TIM_PSCReloadMode_Immediate);
}
//把写入PSC的值封装成一个可读性高的函数。

到这里实现了LED灯以百分之20的功率输出。但我们要实现呼吸灯的话,还要实时调整CCR的值,让他在0-100之间均匀的变化。

有一个库函数可以设置/写入CCR的值,TIM_SetCompare4(TIM2,X);,这个函数可以实时指定输出比较单元的CCR。

1
2
3
4
5
TIM_SetCompare1(TIM2,X);
TIM_SetCompare2(TIM2,X);
TIM_SetCompare3(TIM2,X);
TIM_SetCompare4(TIM2,X);
//写入/设置TIM2不同通道的CCR的值。

因此可以把TIM_SetCompare4(TIM2,X);放到主程序的for循环中,这样就可以实时增加/减少PWM的占空比,从而实现呼吸灯。

具体如下:

1
2
3
4
5
6
7
8
9
10
11
void main(){
for(i=0;i<100;i++)
{
TIM_SetCompare4(TIM2,i);
}
for(j=0;j<100;j++)
{
TIM_SetCompare4(TIM2,100-i);
}
//占空比从0-100,再从100-0;
}

引脚重映射(暂时了解)

针对TIM2的引脚重映射

!注重映射的配置寄存器位于 AFIO 模块内:重映射的配置信息(比如把 TIM2_CH1 从 PA0 重映射到 PA15)是存储在 AFIO 模块内部的寄存器(如 AFIO_MAPR)中的。

1
2
3
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //先开启AFIO引脚。
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
//使用图中第二栏,部分重映射,将TIM2_CH1_ETR功能从PA0重映射到PA15,同时TIM2_CH2功能从PA1重映射到PB3,剩下两个通道不变。

PWM控制直流电机转速(不包括方向)

主要是C语言逻辑这一块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uint8_t i;
uint8_t j=30;
while (1)
{
i=Key_GetNum(); //封装的按键函数,用i获取。
if (i==1) {
j-=10;
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);//配置两个GPIO来确定方向。
if (j==0) {
j=30;
}
PWM_SetCompare3(j);//输出RCC的值,这里直接等于PWM的占空比。
}
OLED_ShowSignedNum(1, 7, j, 3);
}

PWMI模版

输入捕获部分,任何从外部输入的信号想要进入定时器,都需要配置TIM_ICInit

先配置GPIO,然后时基单元(TIM),然后输入捕获单元(IC)初始化(包括选择通道,滤波器,边沿检测,极性选择,分频器),然后触发源选择,从模式选择,启动定时器。

计数值存在CCR1中。

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
void IC_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIO配置

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//因为TIM2用来输出波形,所以TIM3用来接收波形
TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 72-1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 65536-1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//时基单元配置

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter=0x03;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
//选择触发信号从直连通道输入还是交叉通道输入。
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
//输入捕获单元

TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//触发源选择
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//从模式选择
TIM_Cmd(TIM3, ENABLE);
//启动定时器
}
uint16_t IC_GetFreq(){
return 1000000/TIM_GetCapture1(TIM3);
}
//读取CCR的值结合公式封装成获取频率的函数。

有一个函数可以实时读取CCR的值,就是TIM_GetCapture1(TIMX);结果会返回一个uint16_t的整型常量。

1
2
3
4
5
	TIM_GetCapture1(TIM3);
TIM_GetCapture2(TIM3);
TIM_GetCapture3(TIM3);
TIM_GetCapture4(TIM3);
//读取定时器3不同通道的CCR的值,并输出/返回一个整型常量。

PWMI双通道

前面GPIO,TIM基本不变,主要是输入捕获部分要多设置一个通道。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
TIM_ICInitTypeDef  TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter=0x03;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
//通道一。
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter=0x03;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
//通道二选择触发信号为交叉通道输入。
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInit(TIM3, &TIM_ICInitStructure);//每个通道又要初始化设置一下。
//通道二。
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//TI1FP1,触发源选择了通道一,而非通道二。
//从模式选择
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//启动定时器
TIM_Cmd(TIM3, ENABLE);

获取频率和占空比的函数封装

1
2
3
4
5
6
7
uint16_t IC_GetFreq() {
return 1000000/TIM_GetCapture1(TIM3);
//TIM_GetCapture1(TIM3)是读取CCR的值
}
uint16_t IC_GetDuty() {
return TIM_GetCapture2(TIM3)*100/TIM_GetCapture1(TIM3);
}

双通道测频率和占空比原理

对于上图用来捕获频率和PWM占空比。输入捕获部分设置了两路通道,一路上升沿触发,一路下降沿,而触发源则特定选择了通道一,来触发从模式Reset(计数清零)。所以每次上升沿来了,计数都会清零。但清零前,STM32硬件电路会把计数器CNT的值存到CCR1中,这是硬件电路自己完成的,不需要软件操作。这样我们就通过CNT的计数,知道了一个周期的时间。而通道二下降沿来临,计数器CNT的值会计入到CCR2中。这样就通过CCR2的计数知道了高电平占总周期的比例,即占空比。

定时器的编码器接口及其库函数

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
void Encoder_Interface_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//GPIO配置

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 1-1; //不分频PSC
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//没用
//因为目前定时器的计数模式被编码器接口托管,内部本身不计数。
TIM_TimeBaseStructure.TIM_Period = 65536-1;//自动重装值, 满量程计数ARR
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//定时器配置

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICFilter=0x0A;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICFilter=0x0A;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
//输入捕获部分配置,要配置两路

TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
////编码器接口配置

TIM_Cmd(TIM3, ENABLE);
//开启定时器
}


int16_t GetCounter(void) {
int16_t Temp =0;
Temp=TIM_GetCounter(TIM3); //TIM_GetCounter()是获取CNT的值的库函数
TIM_SetCounter(TIM3,0); //TIM_SetCounter()是写入CNT的值的库函数
return Temp;
}
//在主函数中用OLED显示,并Delay_ms(1000),及得到旋转编码器的速度。

//---------------------用中断实现----------------------------//
//首先在Timer函数中已经配置好了TIM2,内部时钟计数,且1s钟触发一次中断//
//在主函数中的main函数下面写入中断函数//
void TIM2_IRQHandler() {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
counter=TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}


ADC

DMA工作的三个条件:

  1. 传输计数器不为0。
  2. DMA使能。
  3. 触发源(软件触发/硬件触发)有信号。

单通道,非连续转换,非扫描模式

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
void AD_Init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;//AIN是ADC专属模式,模拟输入
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
//GPIO配置,A1引脚被初始化为模拟输入引脚

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
//开启ADC1的时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//对ADC进行分频,72/6=12MHz
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5 );
//点菜菜单:选择规则组,在ADC中16个序列中的序列1写入通道1,采样时间是28.5个ADC时钟周期。其中一个ADC时钟周期的时间为1/12,000,000秒,那么28.5个周期时间约为2.375微秒(us)
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_Mode=ADC_Mode_Independent;//设置 ADC 为独立模式
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//不使用外部触发转换,也就是软件触发,使用库函数ADC_SoftwareStartConvCmd(ADC1, ENABLE);来执行转换开始
ADC_InitStruct.ADC_ScanConvMode = DISABLE;//关闭扫描模式(即非扫描模式)。
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//ENABLE:连续转换模式,DISENABLE:单次转换模式
ADC_InitStruct.ADC_NbrOfChannel=1;//通道数目,目前是一个通道
ADC_Init(ADC1, &ADC_InitStruct);
ADC_Cmd(ADC1, ENABLE);//开启ADC1,等待工作,等待触发源

//校准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);//软件触发转换指令
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET){}
///死循环等待,直到 ADC 转换完成标志位(EOC, End Of Conversion)被置位
return ADC_GetConversionValue(ADC1);
//在 STM32 中,当你调用这个函数读取数据时,硬件会自动把刚才的 EOC(转换完成)标志位清零,为下一次转换做好准备。
}

单通道,连续转换,非扫描模式

把上述代码AD_Init函数中的结构体配置中ADC_ContinuousConvModeDISABLE修改为ENABLE

并且自始至终只需要一次软件触发开启AD转换,因此直接把这个指令塞进初始化函数里即可。

1
2
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发转换指令,加在最后。

而下面的uint16_t AD_GetValue(void)函数只需要return ADC_GetConversionValue(ADC1);即可,不需要判断EOC标志位是否被置位,因为全程连续转换,并不是单次转换。

1
2
3
4
uint16_t AD_GetValue(void){
return ADC_GetConversionValue(ADC1);
//在 STM32 中,调用这个函数读取数据。
}

单通道模式实现多通道模式,非连续转换,非扫描模式(不用硬件电路,而是用代码实现)

AD_Init函数中配置通道的函数:ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5 );把第二个参数ADC_Channel_1变成一个变量,然后在循环中不断输入不同的通道,因为每次转换都在几微秒内完成,因此在人眼来看,就是4个通道同时进行。

具体实现,先把ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5 );拿出来,然后封装一个需要输入形参的函数,然后把形参输入到第二个变量位置,即可实现。

1
2
3
4
5
6
7
uint16_t AD_GetValue(uint8_t ADC_Channel) {
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_28Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)==RESET){}
//死循环等待,直到 ADC 转换完成标志位(EOC, End Of Conversion)被置位。
return ADC_GetConversionValue(ADC1);
}

然后在主函数的循环中,写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while (1)
{
AD1=AD_GetValue(ADC_Channel_1);
AD2=AD_GetValue(ADC_Channel_2);
AD3=AD_GetValue(ADC_Channel_3);
AD4=AD_GetValue(ADC_Channel_4);
OLED_ShowString(1,1,"AD1:");
OLED_ShowString(2,1,"AD2:");
OLED_ShowString(3,1,"AD3:");
OLED_ShowString(4,1,"AD4:");
OLED_ShowNum(1,5,AD1,5);
OLED_ShowNum(2,5,AD2,5);
OLED_ShowNum(3,5,AD3,5);
OLED_ShowNum(4,5,AD4,5);
}

表示变量地址

在stm32中,所有的地址都是16进制的,也就是0x……。

1
2
3
4
5
6
7
8
9
10
11
12
13
const uint8_t a=66;
uint8_t b=77;
int main(void)
{
OLED_Init();
while (1)
{
OLED_ShowNum(1,1,a,3);
OLED_ShowHexNum(2,1,(uint32_t)&a,8);//地址都是16进制的,所以要用ShowHexNum。
OLED_ShowNum(3,1,b,3);
OLED_ShowHexNum(4,1,(uint32_t)&b,8);//地址都是16进制的,所以要用ShowHexNum。
}
}

现象如下:

由上图结合代码可知,在变量a前加入const,那么a就被存储到了Flash中,成为了常量。而b作为uint8_t的变量,则被存储到了SRAM中。

所以如果有一个很大的表或者数据库,一般不需要修改,那就在前面加上const,让他存放在FLash中,而非SRAM中。

ADC1->DR“去那个地址把数据拿回来”

&ADC1->DR 才是“那个地址本身”

DMA转运数据

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
uint8_t Transfer_Size;
void My_DMAInit(uint32_t AddrA , uint32_t AddrB , uint8_t Size) {
Transfer_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_PeripheralDataSize_Byte; //每次转运数据大小,这里是一个字节,与源端统一
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //地址是否自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作为数据源,还有一个参数是DMA_DIR_PeripheralDST,这个是外设作为目的地
DMA_InitStructure.DMA_BufferSize = Size; //传输计数器,每次转运一次,这个数就减一,减到0就停止转运/转运完成
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //模式,是否循环,这里的Normal是不循环,如果是DMA_Mode_Circular那就是循环
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //M2M是软件转运,这里是开启软件转运
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置优先级,这里就一个DMA,所以随便设置
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //M2M,存储器到存储器
DMA_Cmd(DMA1_Channel1, DISABLE); //先不使能,在下面的函数里使能
}

void DMA_Transfer(void) {
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, Transfer_Size); //自己设置传输计数器。
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1==RESET)); //检测转运标志位,判断是否转运完成
DMA_ClearFlag(DMA1_FLAG_TC1); //转运完成了,记得手动清除标志位
}

ADC扫描模式+DMA

1

串口USART通信

配置流程图如下:

TX设置复用输出,RX设置输入。图片中间部分全部通过结构体配置。

发送部分:

初始化封装代码:

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
void Serial_Init(void){
RCC_APb2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//开启USART1和GPIOA的时钟

GPIO_InitTypeDef GPIO_InitStructure1;
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA, &GPIO_InitStructure1);
//设置GPIOA_9为复用输出

GPIO_InitTypeDef GPIO_InitStructure2;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure2.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure2.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure2);
//设置GPIOA_10为上拉输入

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; //比特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长8位,不需要奇偶校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验位选择不校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //串口模式选择——发送和接收
USART_Init(USART1, &USART_InitStructure);
//USART配置

USART_Cmd(USART1,ENABLE);
//USART1使能
} //初始化USART串口模块,这里已经包含了发送+接收的功能配置

void Serial_SendByte(uint8_t data){
USART_SendData(USART1,data);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){}
} //发送单字节

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

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

uint32_t Serial_Pow(uint16_t X,uint16_t Y) {
uint16_t res=1;
while (Y--){
res*=X;
}
return res;
} //幂运算函数工具,用来计算X的Y次幂

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

拆分数字的思路

12345/10000%10=1

12345/1000%10=2

12345/100%10=3

12345/10%10=4

12345/1%10=5

想取出某个数的第x位,就把这个数除以(10的(x-1)次方),再对10取余。

发送部分主函数代码

1
2
3
4
5
6
7
8
int main(void){
OLED_Init();
Serial_Init();
while(1){
Serial_SendByte(0x4A);
Delay_ms(1000);
}
}

接收部分(查询模式或中断模式)

查询模式:

查询模式中初始化封装代码:
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
uint8_t ReceiveData;
uint8_t Serial_RxFlag;
void Serial_Init(void){
RCC_APb2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//开启USART1和GPIOA的时钟

GPIO_InitTypeDef GPIO_InitStructure1;
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA, &GPIO_InitStructure1);
//设置GPIOA_9为复用输出

GPIO_InitTypeDef GPIO_InitStructure2;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure2.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure2.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure2);
//设置GPIOA_10为上拉输入

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; //比特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长8位,不需要奇偶校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验位选择不校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //串口模式选择——发送和接收
USART_Init(USART1, &USART_InitStructure);
//USART配置

USART_Cmd(USART1,ENABLE);
//USART1使能
} //USART串口模块初始化配置函数,已经包含了发送及接收部分
查询模式中主函数:
1
2
3
4
5
6
7
8
9
10
11
int main(void){
OLED_Init();
Serial_Init();
while(1){
if(USART_GetFlagStatus(USART1,USART_FlAG_RXNE)!=RESET){
RXData=USART_ReceiveData(USART1);
Serial_SendByte(RXData);
OLED_ShowHexNum(1,1,RXData,2);
}
}
}

USART_FLAG_RXNE 是什么?

RXNE = Receive Not Empty,接收非空标志位。

硬件逻辑:

当串口硬件收到 1 字节数据,存入接收寄存器 DR 后,硬件自动把 RXNE 标志置 1;

当你调用 USART_ReceiveData() 读取数据后,硬件自动清零该标志。

中断模式:

中断模式初始化封装函数:
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
uint8_t ReceiveData;
uint8_t Serial_RxFlag;
void Serial_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure1;
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA, &GPIO_InitStructure1);

GPIO_InitTypeDef GPIO_InitStructure2;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure2.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure2.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure2);

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; //比特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长8位,因为不需要奇偶校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验选择不校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //串口模式选择发送和接收
USART_Init(USART1, &USART_InitStructure);

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//----------------------产生中断信号---------------------//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置优先级。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

USART_Cmd(USART1, ENABLE);
}

//-------中断函数----------//
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
ReceiveData = USART_ReceiveData(USART1);
Serial_RxFlag=1;
USART_ClearFlag(USART1, USART_FLAG_RXNE);
}
}

//--------把ReceiveData封装一下-------------//
uint8_t Serial_GetData(void) {
return ReceiveData;
}

//-------------标志位判断,在主函数中通过判断这个函数的返回值来确定有无数据发送过来,这边来接收-------------------//
uint8_t Serial_GetFlag(void) {
if (Serial_RxFlag==1) {
Serial_RxFlag=0;
return 1;
}
return 0;
}

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

这个函数是连接串口和中断的桥梁,这个函数产生最初的中断信号。

中断模式主函数
1
2
3
4
5
6
7
8
9
10
11
int main(void){
OLED_Init();
Serial_Init();
while(1){
if(Serial_GetFlag()==1){
RXData=Serial_GetData();
Serial_SendByte(RXData);
OLED_ShowHexNum(1,1,RXData,2);
}
}
}

printf在stm32中重映射到串口

首先:Keil和GCC底层printf函数走的是两条完全不同的路线

Keil MDK + 勾选MicroLIB

pinrtf -> 格式化拆分字符 -> fputc(ch,FILE*) -> 串口发送

MicroLIB库强制所有printf输出走fputc,所以只要重写fputc,printf百分百生效。

这是针对Keil的。

1
2
3
4
int fputc(int ch, FILE *f) {
Serial_SendByte(ch);
return ch;
}

ARM-GCC+newlib-nano+nosys.specs(Clion+Cmake环境)

Newly-nano为了极致精简FLASH,砍掉冗余代码,重构了printf底层链路,不再默认走fputc,出现了两条分枝优先级。

优先级: _io_putchar > _write > fputc

开启nano.specs后,printf优先走最精简路径:_io_putchar,直接跳过fputc,所以如果我重写fputc等于白写。库根本不进入这个函数,串口自然没有数据。

注:newlib-nano 为了省 Flash,砍掉文件流 FILE 缓冲层,设计了**单字符直通接口__io_putchar**:

只要用户实现这个函数,nano 的 printf 直接逐个字符丢进它,绕过 FILE 结构体、绕过 fputc、绕过_write,代码体积最小(这就是你加完立刻出数据的原因)。

1
2
3
4
5
int __io_putchar(int ch)
{
Serial_SendByte(ch);
return ch & 0xFF;
}

这里的printf默认行缓冲,必须在末尾写上\n,才能发送出数据,不然 数据一直存在内存里,不发串口。

1
printf("你好\n");

串口收发HEX数据包

发送HEX数据包Serial.c代码:

1
2
3
4
5
6
7
8
9
10
11
12
uint8_t ReceiveData;
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;
void Serial_Init(void){
//---------跟上文一样-------------//
}
void Serial_SendPacket(void) {
Serial_SendByte(0xFF);//这个是自己封装的函数。
Serial_SendArray(Serial_TxPacket,4);//这个是自己封装的函数。
Serial_SendByte(0xFE);//这个是自己封装的函数。
}

发送的数据包分为包头,数据,包尾三部分。

包头为0xFF。

数据为Serial_TxPacket,这里固定为4个字节长度的数组。

包尾为0xFE。

包头和包尾的作用是在接受时用来校准数据包用的。

发送HEX数据包Main.c代码:

在主函数中主要定义Serial_TxPacket的四个值,然后输出Serial_SendPacket()。

接收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
uint8_t ReceiveData;
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;

void Serial_Init(void){
//---------跟上文一样-------------//
}
//-----------------重点:中断函数,使用了状态机思想,设置了一个RxState参数,并分别设置了他为0,1,2时的情况--------------------//
void USART1_IRQHandler(void) {
static uint8_t RxState=0;
static uint8_t pRxPacket=0;

if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t RxData=USART_ReceiveData(USART1);
if (RxState==0) {
if (RxData==0xFF) {
RxState=1;
pRxPacket=0;
}
}
else if (RxState==1) {
Serial_RxPacket[pRxPacket]=RxData;
pRxPacket++;
if (pRxPacket>=4) {
RxState=2;
}
}
else if (RxState==2) {
if (RxData==0xFE) {
RxState=0;
Serial_RxFlag=1;
}
}
}
}

uint8_t Serial_GetData(void) {
return ReceiveData;
} //--------------将获取数据封装成函数-------------//

uint8_t Serial_GetFlag(void) {
if (Serial_RxFlag==1) {
Serial_RxFlag=0; //自动将标志位归0,非常方便。
return 1;
}
return 0;
} //--------------将标志位封装成函数-------------//

接收HEX数据包Main.c代码:

1
2
3
4
5
6
7
8
9
10
11
12
void main(void){
OLED_Init();
Serial_Init();
while(1){
if (Serial_GetFlag() == 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);
}
}
}

I2C配置MPU6050

软件模拟I2C

DR/SR/CR

DR: Data Register(数据寄存器)

SR: Status Register(状态寄存器)

CR: Control Register(控制寄存器)

首先先写My_I2C.c/h,然后在通过My_I2C来配置MPU6050的寄存器,封装到MPU6050.c/h。

My_I2C.c

·先写三个函数,MyI2C_W_SCL,MyI2C_W_SDA,MyI2C_R_SDA,本质上是封装一下翻转GPIO电平和读取GPIO电平。

·初始化I2C,本质上是初始化GPIO。

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
void MyI2C_W_SCL(uint8_t BitValue) {
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue) {
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}

uint8_t MyI2C_R_SDA(void) {
uint8_t BitValue;
BitValue=GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}

void MyI2C_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出模式下可以输出/输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
//把PB10和PB11都置为高电平
}

通过MyI2C_W_SCL,MyI2C_W_SDA,MyI2C_R_SDA三个函数来配置I2C的6个过程:

·void MyI2C_Start(void);

·void MyI2C_Stop(void);

·void MyI2C_SendByte(uint8_t Byte);

·uint8_t MyI2C_ReceiveByte(void);

·void MyI2C_SendAsk(uint8_t AckBit);

·uint8_t MyI2C_ReceiveAsk(void);

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
void MyI2C_Start(void) {
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
//把SDA和SCL都释放到高电平

MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
//先拉低SDA,再拉低SCL
}

void MyI2C_Stop(void) {
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
}

void MyI2C_SendByte(uint8_t Byte) {
uint8_t i;
for (i=0;i<8;i++) {
MyI2C_W_SDA(Byte & (0x80>>i));
/*用按位与的方式,取出数据的某一位
*0x80就是1000 0000,就是再与上Byte,就是取出Byte的最高位
*>>i表示右移i位,第一次右移0位,第二次右移1位,等等
*/
MyI2C_W_SCL(1);//释放SCL之后,从机会立刻把SDA数据读走
MyI2C_W_SCL(0);//拉低SCL,就可以放下一位数据了
}
}

uint8_t MyI2C_ReceiveByte(void) {
uint8_t i;
uint8_t Byte=0x00;
MyI2C_W_SDA(1);
for (i=0;i<8;i++) {
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1) {Byte|=(0x80>>i);}
MyI2C_W_SCL(0);
}
/*这里首先把Byte定义为0000 0000,
*在for循环8次中,如果哪次接收到的数据为1,就通过按位与的方式让这一位变成1。
*如果接收到的数据为0,则不需要进入if语句中,这一位就还是0。*/
return Byte;
}

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

uint8_t MyI2C_ReceiveAsk(void) {
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit=MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}

在写MPU6050.c之前,需要单独新建一个头文件,MPU6050_Reg.h

这里面用宏定义,来把寄存器地址0xXX换成英文,通俗易懂,,方便阅读

MPU6050_Reg.h

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
#define	MPU6050_SMPLRT_DIV		0x19//分频寄存器
#define MPU6050_CONFIG 0x1A//配置寄存器
#define MPU6050_GYRO_CONFIG 0x1B//陀螺仪配置寄存器
#define MPU6050_ACCEL_CONFIG 0x1C//加速度计配置寄存器

//---------------------------------------------------//
//下面是加速器计和陀螺仪的XYZ轴数据的高8位和低8位数据寄存器的地址
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C

#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E

#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40

#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42

#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44

#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46

#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48

//---------------------------------------------------//
#define MPU6050_PWR_MGMT_1 0x6B//电源管理配置寄存器1
#define MPU6050_PWR_MGMT_2 0x6C//电源管理配置寄存器2
#define MPU6050_WHO_AM_I 0x75

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
void MPU6050_Init(void) {
MyI2C_Init();
MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x01);
//配置电源管理寄存器1
MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);
//配置电源管理寄存器2
MPU6050_WriteRegister(MPU6050_SMPLRT_DIV,0x09);
//配置MPU6050的分频系数,这里0x09代表10分频
MPU6050_WriteRegister(MPU6050_CONFIG,0x06);
//MPU6050的配置寄存器初始化设置为,0x06
MPU6050_WriteRegister(MPU6050_GYRO_CONFIG,0x18);
//陀螺仪配置寄存器,gyroscope,陀螺仪
MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG,0x18);
//加速度计配置寄存器,accelerometer,加速度计
}


//指定地址写
void MPU6050_WriteRegister(uint8_t RegAddress, uint8_t Data) {
MyI2C_Start();
MyI2C_SendByte(0xD0);
MyI2C_ReceiveAsk();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAsk();
MyI2C_SendByte(Data);
MyI2C_ReceiveAsk();
MyI2C_Stop();
}


//指定地址读
int8_t MPU6050_ReadRegister(uint8_t RegAddress) {
MyI2C_Start();
MyI2C_SendByte(0xD0);
MyI2C_ReceiveAsk();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAsk();
MyI2C_Stop();

MyI2C_Start();
MyI2C_SendByte(0xD1); //最后一位为0就是写,为1就是读,这里是读
MyI2C_ReceiveAsk();
int8_t Byte=MyI2C_ReceiveByte();
MyI2C_SendAsk(1); //发送应答,发送1就是不应答,发送0就是应答
MyI2C_Stop();
return Byte;
}

void MPU6050_GetData(int16_t *AX, int16_t *AY, int16_t *AZ,
int16_t *GX, int16_t *GY, int16_t *GZ) {
uint8_t AX_H, AY_H, AZ_H, GX_H, GY_H, GZ_H;
uint8_t AX_L, AY_L, AZ_L, GX_L, GY_L, GZ_L;
AX_H=MPU6050_ReadRegister(MPU6050_ACCEL_XOUT_H);
AX_L=MPU6050_ReadRegister(MPU6050_ACCEL_XOUT_L);
*AX=((AX_H<<8)|AX_L);
AY_H=MPU6050_ReadRegister(MPU6050_ACCEL_YOUT_H);
AY_L=MPU6050_ReadRegister(MPU6050_ACCEL_YOUT_L);
*AY=((AY_H<<8)|AY_L);
AZ_H=MPU6050_ReadRegister(MPU6050_ACCEL_ZOUT_H);
AZ_L=MPU6050_ReadRegister(MPU6050_ACCEL_ZOUT_L);
*AZ=((AZ_H<<8)|AZ_L);
GX_H=MPU6050_ReadRegister(MPU6050_GYRO_XOUT_H);
GX_L=MPU6050_ReadRegister(MPU6050_GYRO_XOUT_L);
*GX=((GX_H<<8)|GX_L);
GY_H=MPU6050_ReadRegister(MPU6050_GYRO_YOUT_H);
GY_L=MPU6050_ReadRegister(MPU6050_GYRO_YOUT_L);
*GY=((GY_H<<8)|GY_L);
GZ_H=MPU6050_ReadRegister(MPU6050_GYRO_ZOUT_H);
GZ_L=MPU6050_ReadRegister(MPU6050_GYRO_ZOUT_L);
*GZ=((GZ_H<<8)|GZ_L);
}


uint8_t MPU6050_GetID(void) {
return MPU6050_ReadRegister(MPU6050_WHO_AM_I);
}

Main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(void)
{
OLED_Init();
MPU6050_Init();
int16_t AX, AY, AZ;
int16_t GX, GY, GZ;
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);
OLED_ShowString(1,1,"ID:");
OLED_ShowHexNum(1,4,MPU6050_GetID(),2);
}
}

硬件模拟I2C

需要更改I2C的初始化,需要开启I2C硬件外设,以及对外设进行初始化配置。

然后对于MPU6050的读写,需要用库函数来进行操作。

MyI2C_Hardware.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
void MyI2C_Hardware_Init(void) {
//GPIO初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
//复用开漏输出模式:
//复用:GPIO的控制权交给外设
//开漏:I2C设计要求
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);


//注意,使用的是I2C1
//I2C初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 100000; //100kHz
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1= 0x00;
I2C_Init(I2C1, &I2C_InitStructure);

//I2C使能
I2C_Cmd(I2C1, ENABLE);
}

MPU6050_Hardware.c

其中主要是修改MPU6050的读与写函数,其他与软件I2C的MPU6050函数差不多

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
164
165
166
167
168
169
170
171
172
173
174
uint8_t MPU6050_WaitEvent(I2C_TypeDef *I2Cx,uint32_t event)
{
uint32_t timeout = 100000;

while (I2C_CheckEvent(I2Cx, event) == ERROR)
{
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
{
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
I2C_GenerateSTOP(I2Cx, ENABLE);
return 1;
}

if (--timeout == 0)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return 2;
}
}
return 0;
}

void MPU6050_WriteRegister_I2CHardware(uint8_t RegAddress, uint8_t Data) {
I2C_GenerateSTART(I2C1, ENABLE); //硬件I2C生成起始条件
MPU6050_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5

I2C_Send7bitAddress(I2C1, 0xD0, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
MPU6050_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6

I2C_SendData(I2C1, RegAddress); //硬件I2C发送寄存器地址
MPU6050_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8

I2C_SendData(I2C1, Data); //硬件I2C发送数据
MPU6050_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2

I2C_GenerateSTOP(I2C1, ENABLE);
}

//----------------------------------------------------------------------------------------------------
uint8_t MPU6050_ReadRegister_I2CHardware(uint8_t regAddress)
{
uint8_t data;
uint32_t timeout;
volatile uint32_t clear;

timeout = 100000;

while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET)
{
if (--timeout == 0)
{
return 0;
}
}

I2C_GenerateSTART(I2C1, ENABLE);

if (MPU6050_WaitEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != 0)
{
return 0;
}

I2C_Send7bitAddress(I2C1,0xD0,I2C_Direction_Transmitter);

if (MPU6050_WaitEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != 0)
{
return 0;
}

I2C_SendData(I2C1, regAddress);

if (MPU6050_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != 0)
{
return 0;
}

I2C_GenerateSTART(I2C1, ENABLE);

if (MPU6050_WaitEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != 0)
{
return 0;
}

I2C_AcknowledgeConfig(I2C1, DISABLE);

I2C_Send7bitAddress(I2C1,0xD0,I2C_Direction_Receiver);

timeout = 100000;

while (I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR) == RESET)
{
if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET)
{
I2C_ClearFlag(I2C1, I2C_FLAG_AF);
I2C_GenerateSTOP(I2C1, ENABLE);
I2C_AcknowledgeConfig(I2C1, ENABLE);
return 0;
}

if (--timeout == 0)
{
I2C_GenerateSTOP(I2C1, ENABLE);
I2C_AcknowledgeConfig(I2C1, ENABLE);
return 0;
}
}

clear = I2C1->SR1;
clear = I2C1->SR2;
(void)clear;

I2C_GenerateSTOP(I2C1, ENABLE);

timeout = 100000;

while (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET)
{
if (--timeout == 0)
{
I2C_AcknowledgeConfig(I2C1, ENABLE);
return 0;
}
}

data = I2C_ReceiveData(I2C1);

I2C_AcknowledgeConfig(I2C1, ENABLE);

return data;
}

void MPU6050_Init(void) {
MyI2C_Hardware_Init();
MPU6050_WriteRegister_I2CHardware(MPU6050_PWR_MGMT_1, 0x00);
//配置电源管理寄存器1
MPU6050_WriteRegister_I2CHardware(MPU6050_PWR_MGMT_2, 0x00);
//配置电源管理寄存器2
MPU6050_WriteRegister_I2CHardware(MPU6050_SMPLRT_DIV,0x09);
//配置MPU6050的分频系数,这里0x09代表10分频
MPU6050_WriteRegister_I2CHardware(MPU6050_CONFIG,0x06);
//MPU6050的配置寄存器初始化设置为,0x06
MPU6050_WriteRegister_I2CHardware(MPU6050_GYRO_CONFIG,0x18);
//陀螺仪配置寄存器,gyroscope,陀螺仪
MPU6050_WriteRegister_I2CHardware(MPU6050_ACCEL_CONFIG,0x18);
//加速度计配置寄存器,accelerometer,加速度计
}

void MPU6050_GetData(int16_t *AX, int16_t *AY, int16_t *AZ,
int16_t *GX, int16_t *GY, int16_t *GZ) {
uint8_t AX_H, AY_H, AZ_H, GX_H, GY_H, GZ_H;
uint8_t AX_L, AY_L, AZ_L, GX_L, GY_L, GZ_L;
AX_H=MPU6050_ReadRegister_I2CHardware(MPU6050_ACCEL_XOUT_H);
AX_L=MPU6050_ReadRegister_I2CHardware(MPU6050_ACCEL_XOUT_L);
*AX=((AX_H<<8)|AX_L);
AY_H=MPU6050_ReadRegister_I2CHardware(MPU6050_ACCEL_YOUT_H);
AY_L=MPU6050_ReadRegister_I2CHardware(MPU6050_ACCEL_YOUT_L);
*AY=((AY_H<<8)|AY_L);
AZ_H=MPU6050_ReadRegister_I2CHardware(MPU6050_ACCEL_ZOUT_H);
AZ_L=MPU6050_ReadRegister_I2CHardware(MPU6050_ACCEL_ZOUT_L);
*AZ=((AZ_H<<8)|AZ_L);
GX_H=MPU6050_ReadRegister_I2CHardware(MPU6050_GYRO_XOUT_H);
GX_L=MPU6050_ReadRegister_I2CHardware(MPU6050_GYRO_XOUT_L);
*GX=((GX_H<<8)|GX_L);
GY_H=MPU6050_ReadRegister_I2CHardware(MPU6050_GYRO_YOUT_H);
GY_L=MPU6050_ReadRegister_I2CHardware(MPU6050_GYRO_YOUT_L);
*GY=((GY_H<<8)|GY_L);
GZ_H=MPU6050_ReadRegister_I2CHardware(MPU6050_GYRO_ZOUT_H);
GZ_L=MPU6050_ReadRegister_I2CHardware(MPU6050_GYRO_ZOUT_L);
*GZ=((GZ_H<<8)|GZ_L);
}

uint8_t MPU6050_GetID(void) {
return MPU6050_ReadRegister_I2CHardware(MPU6050_WHO_AM_I);
}

main.c

和软件模拟I2C来读取MPU6050的main.c一模一样。

BKP备份寄存器

STM32硬件设计一条硬性保护逻辑:

默认状态下,CPU不能读写后备域寄存器,防止主电源波动时误改写备份数据。

其中后备域包括:

·BKP 备份寄存器

·LSE 外部低速晶振电路

· RTC实时时钟模块

而解锁后备域的开关在PWR外设里,因此想设置BKP,需要先解锁外设域,而解锁外设域需要先开启PWR的时钟,然后调用PWR_BackupAccessCmd(ENABLE);解锁函数。

BKP初始化函数

1
2
3
4
5
6
//BKP备份寄存器
void BKP_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//开启PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);//开启BKP时钟
PWR_BackupAccessCmd(ENABLE);//解锁后备域函数
}

BKP写/读操作

1
2
3
4
5
6
7
8
9
int main(void){
OLED_Init();
BKP_Init();
BKP_WriteBackupRegister(BKP_DR1,0x2233); //在DR1寄存器里写入0x2233
OLED_ShowHexNum(1,1,BKP_ReadBackupRegister(BKP_DR1),4); //读取DR1寄存器中的值,并显示到1行1列
while(1){

}
}

RTC

MyRTC.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
#include "MyRTC.h"
#include "stm32f10x.h"
#include "time.h"
uint16_t MyRTC_Time[]={2026,07,05,10,01,55};
void MyRTC_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);

//开启LSE时钟
RCC_LSEConfig(RCC_LSE_ON);
//等待LSE启动完成,获取标志位函数返回值为1说明等待完毕
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){}
//选择RTCCLK时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能RTCCLK
RCC_RTCCLKCmd(ENABLE);

//安全保障措施,等待同步,等待上一次操作完成,防止因时钟不同步造成bug
RTC_WaitForSynchro();
RTC_WaitForLastTask();

//配置预分频器
RTC_SetPrescaler(32768-1);
RTC_WaitForLastTask();

if (BKP_ReadBackupRegister(BKP_DR1)!=0x11) {
//设置初始时间
MyRTC_SetTime();
BKP_WriteBackupRegister(BKP_DR1, 0x11);
}
/*
* 借助BKP的特性,主电源掉电备份电源没掉电的情况下,BKP内的数值不变,
* 这样就可以通过if判断BKP中的数值来判断是否应该在初始化中重置时间。
*/
}

/*
设置时间的函数
*/
void MyRTC_SetTime() {
//time_t time_cnt;
struct tm time_data;
time_data.tm_year=MyRTC_Time[0]-1900;
time_data.tm_mon=MyRTC_Time[1]-1;
time_data.tm_mday=MyRTC_Time[2];
time_data.tm_hour=MyRTC_Time[3];
time_data.tm_min=MyRTC_Time[4];
time_data.tm_sec=MyRTC_Time[5];
time_t time_cnt=mktime(&time_data);
//把年月日时分秒转化为时间戳秒数
RTC_SetCounter(time_cnt);
//把这个秒数赋值到CNT
RTC_WaitForLastTask();
}

/*
读取时间的函数
*/
void MyRTC_ReadTime(void) {
time_t time_cnt=RTC_GetCounter();
struct tm time_date=*localtime(&time_cnt);
/*
localtime(&time_cnt):
只是把时间戳 time_t 换算成年 / 月 / 日 / 时 / 分 / 秒
它不会凭空生成 2023-01-01
输出什么日期完全取决于你传给它的 time_cnt(RTC 计数器数值)。
*/
MyRTC_Time[0]=time_date.tm_year+1900;
MyRTC_Time[1]=time_date.tm_mon+1;
MyRTC_Time[2]=time_date.tm_mday;
MyRTC_Time[3]=time_date.tm_hour;
MyRTC_Time[4]=time_date.tm_min;
MyRTC_Time[5]=time_date.tm_sec;
}

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
#include "MyRTC.h"
void BKP_Init(void);
int main(void)
{
OLED_Init();
MyRTC_Init();
while (1)
{
OLED_ShowString(1,1,"Data:");
MyRTC_ReadTime();
OLED_ShowNum(2,1,MyRTC_Time[0],4);
OLED_ShowString(2,5,":");
OLED_ShowNum(2,6,MyRTC_Time[1],2);
OLED_ShowString(2,8,":");
OLED_ShowNum(2,9,MyRTC_Time[2],2);
OLED_ShowString(2,5,":");
OLED_ShowNum(3,1,MyRTC_Time[3],2);
OLED_ShowString(3,3,":");
OLED_ShowNum(3,4,MyRTC_Time[4],2);
OLED_ShowString(3,6,":");
OLED_ShowNum(3,7,MyRTC_Time[5],2);
OLED_ShowNum(4,1,RTC_GetCounter(),10);
}
}

WDG(看门狗)

看门狗本质上是一个定时器,在指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号。

STM32内置两个看门狗:

独立看门狗(IWDG):独立工作,对时间精度要求较低。独立使用LSI时钟。

窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用。使用APB1时钟。

独立看门狗框图:

键寄存器控制电路

IWDG超时时间

My_IWDG.c函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "My_IWDG.h"
#include "stm32f10x.h"
void My_IWDG_Init(void) {
//初始化第一步,开启LSI时钟,不需要手动开启。

//解除IWDG_PR和IWDG_RLR的写保护
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
/*IWDG_WriteAccess_Enable是宏定义,其实就是Ox5555*/

//配置预分频
IWDG_SetPrescaler(IWDG_Prescaler_16);
/*IWDG_Prescaler_16也是宏定义,其实是0x02*/

//配置自动重装值
IWDG_SetReload(2500-1);

//启动前先喂一次狗,确保第一次周期为完整周期
IWDG_ReloadCounter();//喂狗库函数

//启动看门狗
IWDG_Enable();
}

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
int main(void)
{
OLED_Init();
MyRTC_Init();
My_IWDG_Init();
Key_Init();
OLED_ShowString(1,1,"IDWG_Test");
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) {
//RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET判断是不是由看门狗复位。
OLED_ShowString(2,1,"IWDGRST");
Delay_ms(500);
OLED_ShowString(2,1," ");
Delay_ms(100);
RCC_ClearFlag();
}
else {
OLED_ShowString(3,1,"RST");
Delay_ms(500);
OLED_ShowString(3,1," ");
Delay_ms(100);
}

while (1)
{
Key_GetNum();//按键有个摁下不松开就一直在while中,这就可以让主程序卡在这个函数中,导致定时喂狗,导致触发看门狗复位。
IWDG_ReloadCounter();//喂狗
OLED_ShowString(4,1,"FEED");
Delay_ms(600);
OLED_ShowString(4,1," ");
Delay_ms(200);
//一共800ms定时喂狗,不会超1000ms
}
}

SPI

软件SPI读写W25Q64(一种Flash存储器)

W25Q64写入操作时:

·写入操作时,必须先写使能。写入后不需要写失能。

·写入操作后,芯片进入忙状态,不响应新的读写操作。

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
void MySPI_W_SS(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_W_SCK(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

void MySPI_W_MOSI(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

uint8_t MySPI_R_MISO(void) {
uint8_t BitValue;
BitValue=GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
return BitValue;
}



void MySPI_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
MySPI_W_SS(1);//把SS默认置为高电平,默认不选中从机
MySPI_W_SCK(0);//计划使用SPI模式0,所以默认低电平
}

void MySPI_Start(void) {
MySPI_W_SS(0);
}

void MySPI_Stop(void) {
MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend) {
uint8_t ByteReceive=0x00;
for (int 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);
}
return ByteReceive;
}

MyW25Q64.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
void MyW25Q64_Init(void) {
MySPI_Init();
}

//读取W25Q64的ID
void MyW25Q64_ReadID(uint8_t *MID,uint16_t *DID) {
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
*MID=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID<<=8; //把数据放到高8位
*DID|=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
//要用或等于,如果只是等于,高8位就变成了0
MySPI_Stop();
}

//写使能
void MyW25Q64_WriteEnable(void) {
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}

//
void MyW25Q64_WaitBusy(void) {
uint32_t Timeout=100000;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE)&0x01)==1) {
Timeout--;
if (Timeout==0) {
break;
}
}
MySPI_Stop();
}

void MyW25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint8_t Count) {
MyW25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address>>16);
MySPI_SwapByte(Address>>8);
MySPI_SwapByte(Address);
for (int i=0;i<Count;i++) {
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
MyW25Q64_WaitBusy();
}

void MyW25Q64_EraseSector(uint32_t Address) {
MyW25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address>>16);
MySPI_SwapByte(Address>>8);
MySPI_SwapByte(Address);
MySPI_Stop();
MyW25Q64_WaitBusy();
}
//W25Q64的所有带地址的指令中的地址都是24位地址
//因此我们必须选一个整型变量的类型大于24,所以就是32,
//但是SwapByte一次只能交换一个字节,也就是8位数据
//所以需要取出23-16位,15-8位,7-0位
void MyW25Q64_ReadData(uint32_t Address ,uint8_t *DataArray, uint8_t Count) {
MyW25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address>>16);
MySPI_SwapByte(Address>>8);
MySPI_SwapByte(Address);
for (int i=0;i<Count;i++) {
DataArray[i]=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
MyW25Q64_WaitBusy();
}

Main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(void)
{
uint8_t Array1[4]={0xA1,0xB2,0xC3,0xD4};
uint8_t Array2[4]={0x00,0x00,0x00,0x00};
OLED_Init();
MyW25Q64_Init();
MyW25Q64_EraseSector(0x00000);
MyW25Q64_PageProgram(0x00000,Array1,4);
MyW25Q64_ReadData(0x00000,Array2,4);
while (1)
{
OLED_ShowHexNum(1,1,Array2[0],4);
OLED_ShowHexNum(2,1,Array2[1],4);
OLED_ShowHexNum(3,1,Array2[2],4);
OLED_ShowHexNum(4,1,Array2[3],4);
}
}

硬件SPI读取W25Q64

先配置GPIO,再通过一个结构体配置SPI硬件电路,最后开启硬件SPI,SPI_Cmd

SPI.c

SS仍然用软件模拟,因为用软件模拟非常简单

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
void MySPI_W_SS(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);

SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //SS,这里用软件模拟的
SPI_InitStructure.SPI_CRCPolynomial = 7; //固定7,没有为什么
SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);//使能SPI外设

MySPI_W_SS(1);
}

void MySPI_Start(void) {
MySPI_W_SS(0);
}

void MySPI_Stop(void) {
MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend) {
uint8_t ByteReceive=0x00;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET);
SPI_I2S_SendData(SPI1, ByteSend);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET);
return SPI_I2S_ReceiveData(SPI1);
}

Main.c

与软件模拟SPI读取W25Q64的Main一模一样。