STM32介绍和GPIO
最小开发板
- 高速晶振:提供系统高频时钟,保障运行速度。
- 低速晶振:用于低功耗场景,如实时时钟计时。
- 高速晶振为 CPU 主频及高速外设提供时钟,确保系统高性能运行,PLL锁相环9次倍频
- 低速晶振(如 32.768kHz) 专门为 RTC(实时时钟)模块供电,支持低功耗计时,15次分频
STM32F103C8T6
VDD + 高电平 1 VCC
VSS - 低电平 0 GND
时钟的控制下传输数据
ST-LINK
- 电脑和单片机无法直接通信
- USB的电压5V,单片机3.3V
- ST-LINK,转换电压,解决通信
STM32F103内部框图
创建第一个项目
点project-new Project 选择一个目录,文件名为Project
选择STM32F103C8
设置:
在项目下建三个目录
补充位的操作
1.一个16进制等于几个二进制位?4位二进制是1位16进制
2.一个字节Byte是8位二进制,所以一个字节需要两位16进制来表示 0xF1——Byte
3.大部分情况下,STM32单片机的寄存器是32位的, 0xFF11FFFF
4.给一个二进制数1100 0011 ,变成1000 0011
寄存器操作PA0
#include "stm32f10x.h" // Device headerint main()
{//目标:使用PA0引脚,输出高电平//1.开GPIOA的时钟-GPIOA挂在APB2总线上的,(开APB2上面GPIOA的时钟)--APB2 ENABLE Register//RCC->APB2ENR = 0x00000004;//2.配置这个设备(PA0这个引脚),配置成推挽输出(00),配置的输出速度位50MHz(11),所以是3//GPIOA->CRL = 0x00000003;//3.使用这个设备(PA0这个引脚) output Data register//GPIOA->ODR =0x00000001;//目标:使用PC13引脚,输出低电平//1.开GPIOC的时钟-GPIOC挂在APB2总线上的,(开APB2上面GPIOC的时钟),参考6.3.7RCC->APB2ENR = 0x00000010;//2.配置这个设备(PC13这个引脚),配置成推挽输出(00),配置的输出速度位50MHz(11),所以是3,参考8.2.2GPIOC->CRH = 0x00300000;//3.使用这个设备(PC13这个引脚) GPIOC->ODR =0x00000000;//低电平//GPIOC->ODR =0x00002000;//高电平while(1){}}
库函数操作
#include "stm32f10x.h" // Device headervoid delay_ms(int miSec)
{while(miSec--);
}int main()
{//目标:使用PA0引脚,输出高电平//1.开GPIOA的时钟-GPIOA挂在APB2总线上的,(开APB2上面GPIOA的时钟)--APB2 ENABLE RegisterRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//---------------------------------------------------RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);//2. 配置这个设备(PA0这个引脚),配置成推挽输出(00),配置的输出速度位50MHz,GPIO_InitTypeDef GPIO_InitStruct;//定义一个GPIO的结构体GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//模式设置成推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;//引脚是0和1GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//电平切换速度50MHzGPIO_Init(GPIOA, &GPIO_InitStruct);//使用结构体配置PA0引脚//-------------------------------------------------GPIO_InitTypeDef GPIO_InitStruct1;//定义一个GPIO的结构体GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_Out_OD;//模式设置成推挽输出GPIO_InitStruct1.GPIO_Pin = GPIO_Pin_13;//引脚是13GPIO_InitStruct1.GPIO_Speed = GPIO_Speed_50MHz;//电平切换速度50MHzGPIO_Init(GPIOC, &GPIO_InitStruct1);//使用结构体配置PC13引脚//3. 使用这个设备(PA0这个引脚)//GPIO_SetBits(GPIOA,GPIO_Pin_0);//GPIOA的Pin0引脚设置成高电平//GPIO_ResetBits(GPIOA,GPIO_Pin_0);//GPIOA的Pin0引脚设置成低电平 //----------------------------------------------------//GPIO_ResetBits(GPIOC,GPIO_Pin_13);//GPIOC的Pin13引脚设置成高电平while(1){// 点亮PA0,熄灭PC13GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);GPIO_ResetBits(GPIOC, GPIO_Pin_13);delay_ms(500000); // 延时// 熄灭PA0,点亮PC13GPIO_ResetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);GPIO_SetBits(GPIOC, GPIO_Pin_13);delay_ms(500000); // 延时} }
点亮流水灯
#include "stm32f10x.h" // Device headervoid delay_ms(int miSec)
{while(miSec--);
}int main()
{//目标:点亮5个灯,并让5个灯能依次点亮,按照PA0 PA1 PA2 PA3 PA4 PA3 PA2 PA1 PA0这样的顺序循环//1.开GPIOA的时钟-GPIOA挂在APB2总线上的,(开APB2上面GPIOA的时钟)--APB2 ENABLE RegisterRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//2. 配置这个设备(PA0~PA4这个引脚),配置成推挽输出(00),配置的输出速度位50MHz,GPIO_InitTypeDef GPIO_InitStruct;//定义一个GPIO的结构体GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//模式设置成推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;//引脚是0~4GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//电平切换速度50MHzGPIO_Init(GPIOA, &GPIO_InitStruct);//使用结构体配置PA0~PA4引脚//3. 使用这个设备(PA0~PA4引脚)while(1){// 正向循环:PA0→PA1→PA2→PA3→PA4for(int i = 0;i < 5;i++){//GPIO_SetBits(GPIOA, 1 << i); // 点亮当前LEDGPIO_WriteBit(GPIOA, (GPIO_Pin_0 << i), 1); // 点亮当前LED//或者pow(2, i)delay_ms(5000000); // 延时500ms//GPIO_ResetBits(GPIOA, 1 << i); // 熄灭当前LEDGPIO_WriteBit(GPIOA, (GPIO_Pin_0 << i), 0); // 熄灭当前LED //delay_ms(5000000); }// 反向循环:PA3→PA2→PA1→PA0for(int i = 4;i >= 0;i--){GPIO_SetBits(GPIOA, 1 << i); // 点亮当前LED//GPIO_WriteBit(GPIOA, (GPIO_Pin_0 << i), 1); // 点亮当前LEDdelay_ms(5000000); // 延时500msGPIO_ResetBits(GPIOA, 1 << i); // 熄灭当前LED//GPIO_WriteBit(GPIOA, (GPIO_Pin_0 << i), 0); // 熄灭当前LED //delay_ms(5000000); }
// for (int i = 0; i < 8; i++)
// {
// int pin;
// if (i < 5)
// {
// pin = i; // 正向:0→1→2→3→4
// }
// else
// {
// pin = 8 - i; // 反向:3→2→1→0
// }
// }
// GPIO_SetBits(GPIOA, 1 << pin);
// delay_ms(5000000);
// GPIO_ResetBits(GPIOA, 1 << pin);
}
题目 1:
已知变量a
的值为十六进制0x5A
,请使用按位与操作&
,将其与十六进制0x0F
进行按位与运算,计算并输出结果。
要求:
- 请编写代码,使用
&
运算符,并输出结果。
int main()
{int a = 0x5A;a = a & 0x0F;//0101 1010//0000 1111//0000 1010printf("%d", a);//10
}
题目 2:
给定变量b
的值为十六进制0x3C
,使用按位或操作符|
将其与十六进制0x07
进行按位或运算,计算并输出结果。
要求:
- 编写代码,使用
|
运算符,输出运算结果。
int main()
{int a = 0x3C;a = a | 0x07;//0011 1100//0000 0111//0011 1111 printf("%d", a);//63
}
题目 3:
已知变量c
的值为十六进制0xFF
,使用按位取反操作符~
,对其进行按位取反操作,输出结果。
要求:
- 请使用
~
运算符,编写代码并输出取反后的结果。
int main()
{int c = 0xFF; // 取反操作// 原始值:00000000 00000000 00000000 11111111// 取反后:11111111 11111111 11111111 00000000,这是补码//除符号位取反得到反码:10000000 00000000 00000000 11111111//加一得到原码:10000000 00000000 00000001 00000000c = ~c;printf("按位取反后的十进制结果:%d\n", c); // -256printf("按位取反后的十六进制结果:0x%X\n", c); // 0xFFFFFF00(计算机中有符号整数通常以补码形式显示) return 0;
}
题目 4:
将变量d
的值左移2位。假设d = 0x15
,输出左移后的值。
要求:
- 使用左移操作符
<<
,编写代码并输出移位后的结果。
int main()
{int d = 0x15; // 十六进制 0x15 = 十进制 21// 二进制为:0001 0101// 左移 2 位(每左移一位,相当于乘以 2)// 原始二进制:0001 0101// 左移2位后 0101 0100(低2位补0)// 十六进制结果为:0x54// 十进制结果为:84d = d << 2;printf("左移2位后的十进制结果:%d\n", d); // 输出:84printf("左移2位后的十六进制结果:0x%X\n", d); // 输出:0x54return 0;
}
题目 5:
将变量e
的值右移3位,假设e = 0x98
,输出右移后的值。
要求:
- 使用右移操作符
>>
,编写代码并输出移位后的结果。
#include <stdio.h>int main()
{int e = 0x98; // 十六进制 0x98 = 十进制 152// 二进制为:1001 1000// 右移 3 位(每右移一位,相当于除以 2,整数部分保留)// 原始二进制:1001 1000// 右移3位后: 0001 0011(高位补0,算术右移)// 十六进制结果为:0x13// 十进制结果为:19//算术移位:右边丢弃,左边补原符号位。//逻辑移位:右边丢弃,左边补0。//大多数主流编译器在对 int 做右移时是算术右移,对 unsigned int 则是逻辑右移e = e >> 3;printf("右移3位后的十进制结果:%d\n", e); // 输出:19printf("右移3位后的十六进制结果:0x%X\n", e); // 输出:0x13return 0;
}
题目 6:
给定一个8位的无符号整数f = 0b11001010
,请计算以下操作:
- 将
f
的最低有效位清零(即按位与0b11111110
)。 - 将
f
的第三位(从右数)置为1(即按位或0b00000100
)。
要求:
- 编写代码,使用
&
和|
进行位操作,输出每步操作后的结果。
int main()
{unsigned char f = 0b11001010; // 原始值 f = 0b11001010(即十进制 202)//0b 是 二进制字面量 的前缀// 用二进制表示每一位(f >> i) & 1for (int i = 7; i >= 0; i--) {printf("%d", (f >> i) & 1);}printf("\n");// 1. 将 f 的最低有效位清零(按位与 0b11111110)f = f & 0b11111110; // 清零最低有效位// 2. 将 f 的第三位(从右数)置为 1(按位或 0b00000100)f = f | 0b00000100; // 将第三位(从右数)置为 1return 0;
}
题目 7:
在给定的整数g = 0x2A
中,清除其第4位(从右数,按位与0b11101111
),并在第5位设置1(按位或0b00100000
),输出最后的结果。
要求:
- 请使用
&
和|
,编写代码并输出最后的结果。
#include <stdio.h>int main()
{unsigned char g = 0x2A; // 十六进制 0x2A = 二进制 00101010 = 十进制 42// 第一步:清除第4位(从右数),按位与 0b11101111(即清除 bit3)g = g & 0b11101111;// 第二步:设置第5位(从右数),按位或 0b00100000(即设置 bit5)g = g | 0b00100000;return 0;
}
题目 8:
int t=0x12345678,将t的47位,改为0x3,且1213位,改为0;
要求:
- 请使用
&
和|
,编写代码并输出最后的结果。
int main()
{int t = 0x12345678;//0001 0010 0011 0100 0101 0110 0111 1000printf("改变前:%d\n", t);printf("改变前:0x%08x\n", 0x12345678);//把4~7位,改为0x3 0011//0001 0010 0011 0100 0101 0110 **0111** 1000//1. 先把t的第4~7位清空(变为0)/*先用0000 0000 0000 0000 0000 0000 0000 1111,即0xF,左移4位得0000 0000 0000 0000 0000 0000 1111 0000(即 0xF0)再取反后1111 1111 1111 1111 1111 1111 0000 1111(即 0xFFFFFF0F)最后再或上这个数,得到0001 0010 0011 0100 0101 0110 *0000* 1000*/t &= ~(0xF << 4);//2. 将0x3移位到4~7位/*0x3 的二进制是 0011左移 4 位,使其对齐到 4~7 位0x3 << 4 = 0000 0000 0000 0000 0000 0000 0011 0000最后再与上这个数,得到0001 0010 0011 0100 0101 0110 *0011* 1000*/t |= 0x3 << 4;//且12~13位,改为0//0001 0010 0011 0100 01*01* 0110 0011 1000//1. 直接把t的第12~13位清空(变为0)t &= ~(0x3 << 12);printf("改变后:%d\n", t);printf("改变后:0x%x\n", t); return 0;
}
位运算总结
一个数的某一位,置为0,只需要按位与一个需要置换的位置为0其他位置为1的数,因为&有0则0,一个数&1是本身,不变
一个数的某一位,置为1,只需要按位或一个需要置换的位置为1其他位置为0的数,因为|有1则1,一个数|0是本身,不变
一个数的某一位,进行反转,只需要异或一个需要置换的位置为1其他位置为0的数,因为相同(本身)为0相异为1,一个数0不变,1反转
要清零,用 &
,掩码位是 0
要置 1,用 |
,掩码位是 1
要翻转,用 ^
,掩码位是 1
要取反,用 ~
,整个数变反
Brian Kernighan 算法的核心是利用 n & (n - 1) 这个操作。这个操作能够把整数 n 的二进制表示里最右边的 1 置为 0
//--------------------编写一个代码求一个整数存储再内存中的二进制1的个数-------------------------
int countSetBits(int num)
{int count = 0;// 循环直到 num 变为 0while (num){// 检查最低位是否为 1if (num & 1) {count++;}// 右移一位num >>= 1;// num = num >> 1}return count;
}//-------------------------------------方法二-----------------------int main()
{int num = 15;int count = 0;while (num) {num = num & (num - 1);count++;}printf("%d\n", count);return 0;
}//---------------------修改二进制的某一位-----------------------------int a = 13;
00000000000000000000000000001101
现在要把倒数第二位改成1
需要 | 一个
00000000000000000000000000000010//1<<1,这个数可以理解为1向左移动了1位
即 a | (1 << 1)
a |= 1 << 1;
得到
00000000000000000000000000001111那么现在需要把a = 29
00000000000000000000000000011101
的第五位的1变成0
可以 & 一个
11111111111111111111111111101111
得到
00000000000000000000000000001101
那么11111111111111111111111111101111这个怎么得到
就可以由00000000000000000000000000010000~按位取反得到
即
a &= ~(1<<4);//a=a &~(1<<4)//-------------------不创建临时变量(第三个变量),实现两个整数的交换---------------int main()
{int a = 3;int b = 5;a = a + b;b = a - b;//总和-b得到aa = a - b;//总和-a得到breturn 0;
}但是这个方法会有潜在的问题,
a和b都是整形,都有最大值,
如果a和b都很大,那么a+b就超出了int能存储的最大值,溢出了。int main()
{int a = 3;int b = 5;a = a ^ b;b = a ^ b;a = a ^ b;return 0;
}3 ^ 3 = 0; --->a ^ a = 0;
011
011
000
两个相同的数(正负数都满足)异或为00 ^ 5 = 5; --->0 ^ a = a;
000
101
101
0和一个数(正负数都满足)异或为本身3 ^ 3 ^ 5 = 0 ^ 5 = 5;
3 ^ 5 ^ 3 = 5;
异或操作符支持交换律所以
int a = 3;
int b = 5;
a = a ^ b// a = 3 ^ 5
b = a ^ b;// b = 3 ^ 5 ^ 5 = 3
a = a ^ b;// a = 3 ^ 5 ^ 3 = 5思路:
想要a = b
先a = a ^ b;
b = a ^ b;
就得到了b = a;
现在是b = a; a = a ^ b;
想要a = b;
再a = a ^ b;