基于江科大的stm32单片机学习笔记
STM32引脚定义
STM32F103c8t6引脚定义表:(橙色是电源相关的引脚,蓝色是最小系统相关的引脚。绿色的是IO口和功能引脚,FT意为能容忍5V电压,主功能是上电默认的功能,默认复用功能是IO口上连接的外设功能引脚,加粗的引脚是能直接用的,没加粗的引脚可能要进行其他配置才能使用)
2-1 新建工程
3-1 GPIO输出
GPIO叫通用输入输出端口,有八种配置模式
这里解释一下各种模式(原文章:STM32 GPIO八种输入输出模式 - 知乎 ):
1. GPIO_MODE_AIN 模拟输入
输入信号不经施密特触发器直接接入,输入信号为模拟量而非数字量,其余输入方式输入数字量。
2. GPIO_MODE_IN_FLOATING 浮空输入
输入信号经过施密特触发器接入输入数据存储器。当无信号输入时,电压不确定。因为浮空输入既高阻输入,可以认为输入端口阻抗无穷大,这样可以检测到微弱的信号。(相当于电压表测电压,如果电压表内阻不够大而外部阻抗比较大,则电压表分压会比较小)。此时输入高电平即高电平,输入低电平即低电平。但是外界没有输入时输入电平却容易受到外界电磁以及各种玄学干扰的影响。如按键采用浮空输入,则在按键按下时输入电平为低,但是当松开按键时输入端口悬空,外界有微弱的干扰都会被端口检测到。此时端口可能高,也可能低。
3. GPIO_MODE_IPD 下拉输入
浮空输入在外界没有输入时状态不确定,可能对电路造成干扰。为了使得电路更加稳定,不出现没有输入时端口的输入数据被干扰 (比如手碰一下电压就发生变化)。这时就需要下拉电阻(或上拉电阻),此电阻与端口输入阻抗相比仍然较小。有输入信号时端口读取输入信号,无输入信号时端口电平被拉到低电平(高电平)。
4. GPIO_MODE_IPU 上拉输入
上拉输入与下拉输入类似,只是无输入信号时端口电平被拉到高电平。例如按键信号,当按下时输入低电平,松开时电平被拉到高电平。这样就不会出现按键松开时端口电平不确定的情况。即不知道时按下还是松开。
5. GPIO-MODE_OUT_OD 开漏输出
开漏输出即漏极开路输出。这种输出方式指场效应管漏极开路输出。需要接上拉电阻才能输出1。漏极经上拉电阻接到电源,栅极输出0时,场效应管截止(阻抗无线大),电压被分到场效应管上,此时输出为1。当栅极输出1时,场效应管导通,输出端口相当于接地,此时输出0。开漏输出高电平时是由外接电源输出的,因此可以实现高于输出端口电压的输出。可以实现电平的转换。开漏输出可以实现线与功能,方法是多个输出共接一个上拉电阻。但是漏极开路输出上升沿慢,因为上升沿是外接电源对上拉电阻以及外接负载充电。当上拉电阻较大或者负载容性较大时时间常数较大,充电较慢。需要较快反映时可以采用下降沿触发,此时没有电阻接入,电路的时间常数较小,充电较快。
6. GPIO_MODE_OUT_PP 推挽输出
推挽输出既可以输出1,又可以输出0。但是无法调节输出电压,因为输出高低电平均为三极管输入端电压,此电压在由芯片内部供电,无法改变。推挽输出任意时刻只有一路工作。上图为输出高电平时电路工作状态。只有三极管导通电阻,无外接电阻。因此推挽输出损耗小、速度快。
7. GPIO_MODE_AF_OD 复用开漏输出
STM32单片机内部有其他的外设,比如定时器、DAC等。复用开漏输出与普通开漏输出区别在于,开漏输出输出的是输出数据寄存器中的数据,复用开漏输出输出的是来自外设的数据。
8. GOIO_MODE_AF_PP 复用推挽输出
复用推挽输出原理与复用开漏输出原理相同
官方GPIO相关库函数说明:
下面是标准库有关GPIO的所有函数,可在”stm32f10x_gpio.h”中查看。
1 | //重置函数,将GPIO的所有配置重置,恢复为上电状态void GPIO_DeInit(GPIO_TypeDef* GPIOx); |
3-2 LED闪烁&LED流水灯&蜂鸣器
想要驱动GPIO外设,分三个步骤。使能时钟RCC,初始化GPIO,调函数置高低电平
1 |
|
1 | //任务2 LED流水灯 接A0~A7八颗灯珠,低电平点亮void task02(){ //使能PB2总线时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //初始化GPIOA A0~A7 GPIO_InitTypeDef GPIO_initstructure; GPIO_initstructure.GPIO_Pin = GPIO_Pin_All; GPIO_initstructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_initstructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_initstructure); while(1) { GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0001 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0010 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0100 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 1000 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 0001 0000 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 0010 0000 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 0100 0000 Delay_ms(500); GPIO_Write(GPIOA,~0x0001); //0000 0000 1000 0000 Delay_ms(500); }} |
1 | //任务3 蜂鸣器叫唤 接PB12,低电平触发,有源蜂鸣器 |
3-3 GPIO输入![image-20240216214506811](https://pic-img-picgo.oss-cn-nanjing.aliyuncs.com/picture/202402162145844.png)
1 | typedef struct{ |
3-4 按键控制LED&光敏传感器控制LED
使用模块化编程,当遇到多外设时,将每个设备的代码分成.c和.h分开来写,有利于使代码结构清晰,也便于移植。
mian.c
1 |
|
KEY.c
1 |
|
KEY.h
1 |
|
Delay.c
1 |
|
Delay.h
1 |
|
LED.c
1 |
|
LED.h
1 | // LED.c 用于存放驱动程序的主体代码 |
LED.c (光敏)
1 | // LED.c 用于存放驱动程序的主体代码 |
LED.h (光敏)
1 | // LED.h 用于存放驱动程序可以对外提供的函数或变量的声明 |
light_sensor.c
1 |
|
light_sensor.h
1 |
|
4-1 OLED调试工具
OLED是有机发光二极管的简称,下面提供的OLED驱动函数是江科大自己编写的软件IIC,字库未搭载汉字,不能显示汉字。
相比于硬件IIC,软件IIC更为灵活,任意两个引脚就能驱动OLED,只需在驱动函数稍作修改即可正常使用,但其通信速度相比于硬件IIC来说就较慢了,并且软件IIC会占用CPU的运算资源,硬件IIC和软件IIC各有优劣,按需选择。
4-2 OLED显示屏
1 | void main(){ OLED_Init(); OLED_ShowChar(1,1,'A'); //显示单个字符 OLED_ShowString(1,3,"hello world!"); //显示字符串 OLED_ShowNum(2,1,12345,5); //显示无符号数字 OLED_ShowSignedNum(2,6,-66,3); //显示有符号数字 OLED_ShowHexNum(3,1,0xA0,8); //显示十六进制数 OLED_ShowBinNum(4,1,0xAA55,16); //显示二进制,填入的参数依然为十六进制 while(1) { }} |
OLED.c
更换不同的引脚使用此驱动库时,只要把.c文件内的宏定义和初始化的引脚配置改成自己想要的即可。
OLED.h
OLED_Font.h 即字库文件
5-1EXTI外部中断
使用外部中断的步骤:
1.配置RCC,相关时钟都打开
2.配置GPIO,选择端口为输入模式
3.配置AFIO,链接到EXTI
4.配置EXTI,选择边缘触发方式
5.配置NVIC,给中断配置优先级
官方的EXTI外设库函数解释:
下面是EXTI外部中断库函数的调用函数,位置在stm32f10x_exti.h”内
1 | //将EXTI外设配置恢复位上电时的默认状态 |
“misc.h”中的关于NVIC中断控制器的函数
1 | //设置中断分组的方式 |
5-2 对射使红外传感器计次&旋转编码器计次
这里仅贴核心代码
1 |
|
1 |
|
1 |
|
6-1TIM定时器中断
有关定时器的库函数就更多了,先将”stm32f10x_tim.h”内的函数都列出来大致看一下,用到再详细说明
1 | //将时钟配置恢复到上电时的默认状态 |
6-2 定时器定时中断&定时器外部时钟
main.c
1 |
|
time.c
1 |
|
以上代码是STM32微控制器的一个定时器初始化函数 Timer_Init
。这个函数配置了TIM2定时器,设置了定时器的时钟源、分频系数和计数周期,并启用了定时器更新中断。下面是代码逐行解释:
1 | void Timer_Init(void) |
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
- 使能TIM2定时器的时钟。TIM2属于APB1总线上的外设,因此使用
RCC_APB1PeriphClockCmd
函数使能TIM2时钟。
- 使能TIM2定时器的时钟。TIM2属于APB1总线上的外设,因此使用
TIM_InternalClockConfig(TIM2);
- 配置TIM2使用内部时钟源。
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
- 定义一个
TIM_TimeBaseInitTypeDef
类型的结构体变量,用于配置定时器的基本时间参数。
- 定义一个
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
- 设置时钟分频因子为1,即不分频。
TIM_CKD_DIV1
表示直接使用输入时钟。
- 设置时钟分频因子为1,即不分频。
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
- 设置定时器的计数模式为向上计数。
TIM_CounterMode_Up
表示定时器从0计数到TIM_Period
的值。
- 设置定时器的计数模式为向上计数。
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
- 设置自动重装载寄存器的值为9999。定时器的计数值从0到9999,即计数10000次后产生一次更新事件。
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
- 设置预分频器的值为7199。预分频器将时钟频率分频为
7200
。如果输入时钟为72 MHz,分频后的时钟为 10 kHz。
- 设置预分频器的值为7199。预分频器将时钟频率分频为
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
- 设置重复计数器值为0,对于基本定时器,这个参数一般不使用。
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
- 使用上述配置初始化TIM2定时器。
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
- 使能TIM2的更新中断。当定时器计数达到
TIM_Period
的值时会产生更新中断。
- 使能TIM2的更新中断。当定时器计数达到
计时周期计算
假设输入时钟为72 MHz,分频系数为7200,自动重装载值为10000:
- 计数频率 = 输入时钟 / 预分频值 = 72 MHz / 7200 = 10 kHz
- 计数周期 = (自动重装载值 + 1) / 计数频率 = 10000 / 10 kHz = 1 秒
因此,该配置使得TIM2每秒产生一次更新中断。
6-3TIM输出捕获
PWM配置步骤:
1.开启RCC时钟,将要使用的定时器和GPIO外设时钟打开
2.配置时基单元,时钟源选择
3.配置输出比较单元,设置CCR的值,输出比较模式,极性选择,输出使能这些参数
4.配置GPIO,初始化为复用推挽输出的配置
5.运行控制,启动计数器
6-4 PWM驱动LED呼吸灯&PWM驱动直流电机
一个定时器TIM有四个通道,每个通道均可输出PWM波,但当我们用一个定时器输出4路PWM时,因为共用一个计数器,频率必须是一样的,占空比由各自通道的CCR决定,四路通道的相位也是一样的。
main.c
1 |
|
PWM.c
1 |
|
servo.c
1 |
|
motor.c
1 |
|
6-5 TIM输入捕获
高级定时器和基本定时器有输入捕获通道,但基本定时器没有。
PWMI模式是专门测量输入的频率和占空比的。
TIM输入捕获也可配置为主从触发模式,从而实现硬件全自动测量。
测频率就是测量一秒钟内有几个周期,测频法适用于测量高频率,测周法适用于测低频率,频率是高还是低就看中介频率。
配置输入捕获的步骤:
1.RCC开启时钟,把GPIO和TIM的时钟打开
2.GPIO初始化,将GPIO配置成输入模式,上拉输入或浮空输入
3.配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
4.配置输入捕获单元,包括滤波器、极性、直连通道还是交叉通道,分频器这些参数
5.选择从模式的触发源,这里选择TI1FP1
6.选择触发后执行的操作,执行Reset操作
7.调用TIM_Cmd函数,开启定时器
6-6 输入捕获模式测频率&PWMI模式测频率占空比
main.c
1 |
|
task01.c
1 |
|
task02.c
1 |
|
6-7 TIM编码器接口
配置编码器测量步骤:
1.RCC开启时钟,开启GPIO和定时器时钟
2.配置相关GPIO,将CH1和CH2的IO口设置为输入模式
3.配置时基单元,预分频器一般不分频,自动重装一般给最大65535
4.配置输入捕获单元,只需配置通道、滤波器和极性即可,其他参数用不上
5.配置编码器接口模式,直接调用库函数
6.调用TIM_Cmd启动定时器
6-8 编码器接口测速
main.c
1 |
|
encoder.c
1 |
|
7-1 ADC模数转换器
以下是ADC的库函数:
1 | //恢复初始化 |
7-2 AD单通道&AD多通道
STM32f103c8t6只有ADC1和ADC2,且无PC部分的端口
有四种转换模式
配置ADC的步骤:
1.开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分配器也需配置RCC_用ADCCLKConfig()
2.配置GPIO,将信号输入的端口配置成模拟输入
3.配置多路开关,将左边的通道接入到右边的规则组列表里
4.配置ADC转换器,结构体里面的参数有选择转换模式,是否扫描,通道个数设置,触发源设置,数据对齐方式等。
5.开启ADC,调用ADC_Cmd函数。如若需要,可加入校准步骤。
main.c
1 |
|
task01.c
1 |
|
1 |
|
8-1 DMA直接存储器取存
DMA库函数:
1 |
|
8-2 DMA数据转运&DMA+AD多通道
配置DMA的步骤:
1.启动RCC时钟
2.调用DMA初始化函数DMA_Init,配置参数
3.开启DMA,调用DMA_Cmd
4.(如果要硬件触发,要调用XXX_MDACmd,开启触发信号的输出)
5.(如果要DMA中断,调用DMA_ITConfig,开启中断输出)
main.c
1 |
|
task01.c
1 |
|
task02.c
1 |
|
9-1 USART串口协议
9-2 USART串口外设
9-3 串口发送&串口发送+接收
配置串口步骤:
1.开启时钟,将要用的USART和GPIO时钟打开
2.GPIO初始化,将TX配置成复用输出,RX配置成输入
3.USART初始化,若需要接收功能,则另外要配置中断,只发送就不用配置中断
4.配置中断,加上ITConfig和NVIC的配置
5.开启USART
USART的库函数:
1 |
|
main.c
1 |
|
task01.c
1 |
|
task01.c
1 |
|