目录
前言
由于手边缺少传感器,电阻,LED 等配件,导致 Pi Pico 的学习进度中断。因此直接开始尝试重新拾起 STM32。
本次使用的是 STM32 最小系统板,芯片型号为 STM32F103C8Tb 991KA 93 MYS 801
。
软件准备
参考 参考文章 进行一个 Keil 的安装和配置,在做下面一系列步骤遇到问题时可以看看那几篇:
- 进入官网下载 Keil
- 然后在网上冲浪找到能用的注册机
- 进入 keil 中,点击 “pack installer” 组件安装 STM32F10 包。注意右下角有个进度条,点击 install 后要等它变成 “Up to data” 才表示安装成功
- 新建工程,选择对应的芯片,教程中选的是
STM32F103RC
我选的是STM32F103C8
- 之后勾选需要的库,按照下图所示勾选,后面也可以继续添加,这里给一个 参考连接
- 打开
Target 1
右键Source Group 1
添加新文件Add New Item
选择第一项C File
命名为.c
文件即可,如main.c
- 如果编辑器中无法显示中文,
Edit->Configuration->Editor->Encoding: UTF-8
- 如果编辑器中无法显示中文,
经典点灯
首先熟悉一下如何往 STM32 中烧录程序:
- 往工程中加入 or 新建一个
main.c
文件并写入下面的代码,这个代码是调用了最小系统板上的板载 LED 来进行引脚PC13
的 IO 测试 点击魔法棒->Output->勾选 Creat HEX File
,想看操作截图可以到 这里 看- 依次点击编译按钮,查看代码是否有问题,只要没有 Error 就没关系,点击中间的构建(Build)按钮(或 F7)就会在工程文件同目录的
Objects
生成一个与工程同名的.hex
文件 - 将 STM32 通过 TTL 转 USB 连接到电脑上,工具和接线的图示看 这里
- 打开 mcuisp 下载程序,按照 这里 操作,可能会遇到的问题这里也有
- 最后你可以尝试修改代码,如闪烁中的演示长度,并再次编译(F7)之后下载到 STM32 上
- 这时如果 mcuisp 一直等待连接,你需要按一下板子上的 RESET 按钮重新上电
- 也可以拔掉 USB 转 TTL 再插上电脑,然后再次点击 “开始编程” 按钮
- 如果你想让下载好的程序保持运行,拔掉之后,把跳线帽还原,再用任意方式上电即可
#include "stm32f10x.h"
void delay(uint32_t n) {
uint32_t t;
SysTick->CTRL = 0x00;
SysTick->LOAD = n*(uint16_t)(SystemCoreClock/8000000)*1000;
SysTick->VAL = 0x00;
SysTick->CTRL |= 0x01;
do {t = SysTick->CTRL;} while ((t&0x01)&&!(t&(1<<16)));
SysTick->CTRL = 0x00;
SysTick->VAL = 0x00;
}
void led_init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
int main(void) {
led_init();
while (1) {
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
delay(500);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
delay(500);
}
}
代码分析
头文件
#include "stm32f10x.h"
// #include "stm32f10x_rcc.h"
// #include "stm32f10x_gpio.h"
理论上来说接口结构体 GPIO_InitTypeDef
定义在函数库 stm32f10x_gpio.h
中,GPIO 使能函数 RCC_APB2PeriphClockCmd
定义在函数库 stm32f10x_rcc.h
中。然而实际测试中上面三个库声明其中任意一个程序都可以正常编译,无非是 Warning 的个数会加减 2 个。
学徒我不求甚解,可能跟 keil5 这个编译软件的工作原理有关,暂时先不纠结。
引脚初始化
void led_init(void) {
// 定义 GPIO 结构体,下面初始化接口函数使用
GPIO_InitTypeDef GPIO_InitStruct;
// 初始化引脚工作模式为 通用推挽输出
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
// 使用 PC13 引脚
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
// 初始化引脚速度
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 使能 GPIOC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// 初始化指定引脚
GPIO_Init(GPIOC, &GPIO_InitStruct);
// 使 GPIO 输出高电平,ResetBits 是输出低电平
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
计时器初始化
为了实现精准的延时,我们需要了解单片机的工作频率并加以运用。幸运的是 STM32 使用的 Cortex-M3 架构处理器自带一个简易的滴答计时器(SysTick) 让我们能够实现一个便于理解的计时器。
static uint8_t D_us = 0;
static uint16_t D_ms = 0;
void delay_init(void) {
// 设置系统 SysTick 时钟为系统时钟的 8 分频
// 系统时钟为 72MHZ 所以 SysTick 的时钟为 9MHZ
// 也就是说 SysTick->value 中的值每减少 1 需要花费 1/9 微秒的时间
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
// 获取微秒和毫秒的值
// 微妙系数 = 系统时钟(72MHZ)/8M = 9
D_us = SystemCoreClock/8000000;
// 毫秒系数为微秒的 1000 倍
D_ms = (uint16_t)D_us*1000;
}
D_us D_ms
这两个系数是定义在头文件下面的全局变量,为了在初始化中修改值,并在下面的delay
函数中使用- STM32 可以使用多种系统时钟,这个频率也可以自己定义,但是对于初学者来说,只要知道默认的频率是
72MHZ
即可 - 微妙系数
D_us
可以理解为 “度过1
微妙需要芯片运行的次数/系统震动的次数”- 系统频率
72MHZ
,8
分频后是9MHZ
,即1s
震动9M
次,1us
震动9
次
- 系统频率
- 同理,
1ms = 1000us
,因此1ms
震动9000
次
延时函数
// 微秒延时
void delay_us(uint32_t n) {
uint32_t t;
// 关闭 SysTick 定时器
SysTick->CTRL = 0x00;
// 修改重装载寄存器中的值
// SysTick->LOAD = n*(uint16_t)(SystemCoreClock/8000000)*1000;
SysTick->LOAD = n*D_us;
// 清空计数器
// 计数器 VAL 为 0 时会重载 LOAD 的值,同时 CTRL 寄存器中清除 COUNTFLAG 的标志
SysTick->VAL = 0x00;
// 打开定时器
SysTick->CTRL |= 0x01;
// SysTick->CTRL 第 0 位表示 ENABLE 定位器的使能位
// SysTick->CTRL 第 16 位表示 COUNTFLAG
// 上次读 CTRL 寄存器时 VAL 若为 0,则该位为 1
// 初始化时 VAL 设为 0 后,SysTick->CTRL |= 0x01; 这条代码读取了一下 CTRL 寄存器,此时 16 位为 1,且 VAL 已经重载
// 但接下来的 do while 循环会无条件的先执行一次 t = SysTick->CTRL; 再次读取 CTRL 此时 VAL 不为 0,因此需要等待倒数结束才能得到 t&(1<<16) = 1 从而退出 while 循环
do { t = SysTick->CTRL; } while ((t&0x01) && !(t&(1<<16)));
// 关闭
SysTick->CTRL = 0x00;
// 清空
SysTick->VAL = 0x00;
}
- 配置定时器之前先关闭定时器
SysTick->CTRL = 0x00;
避免数据错乱 SysTick->LOAD
为计数数量,1us
需要震动D_us
次,nus
需要震动n*D_us
次- 打开定时器
SysTick->CTRL |= 0x01;
后,定时器就会开始对系统震动进行计数 do while
循环不断检测打开定时器后震动次数是否达到n*D_us
,次数到达后结束循环
毫秒级延时同理
void delay_ms(uint32_t n) {
uint32_t t;
SysTick->CTRL = 0x00;
SysTick->LOAD = n*D_ms;
SysTick->VAL = 0x00;
SysTick->CTRL |= 0x01;
do { t = SysTick->CTRL; } while ((t&0x01) && !(t&(1<<16)));
SysTick->CTRL = 0x00;
SysTick->VAL = 0x00;
}
甚至可以这样
void delay(uint8_t type, uint32_t n) {
uint32_t t;
SysTick->CTRL = 0x00;
SysTick->LOAD = n*(type?D_ms:D_us);
SysTick->VAL = 0x00;
SysTick->CTRL |= 0x01;
do { t = SysTick->CTRL; } while ((t&0x01)&&!(t&(1<<16)));
SysTick->CTRL = 0x00;
SysTick->VAL = 0x00;
}
点灯
int main(void) {
delay_init();
led_init();
while (1) {
// 输出低电平开灯
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
delay(1, 100000);
// 输出高电平关灯
GPIO_SetBits(GPIOC, GPIO_Pin_13);
delay(1, 100000);
}
}
- 进入
main()
函数首先初始化我们需要用到的模块 - 尝试把
1
改成0
查看代码运行结果
末尾闲言
曾经拿 32 做了一个小电子琴当课设,现在重新学习 32 想复刻却没找到任何资料,只找到一篇自己写的电灯博客。环境搭建倒是记录的挺详细的,里面粘贴的代码却完全没有解释。
本篇入门就先以把初学接触到的代码完全理解为目标进行学习,成果如上,希望能对各位以及未来的我有所帮助。