STM32F1的IO口简介

基本介绍

STM32的引脚可设置为可设置为:普通IO功能、复用功能、重映射功能。不过普通IO功能、复用功能用得比较多。

  1. 普通 IO 功能一般需要使能 GPIOAGPIOBGPIOC 时钟
  2. 需要使用复用功能,如 STM32F103RCT6 的串口1功能,需要使用 PA9PA10 两个引脚,此时需要使用串口1时钟 USART1
  3. 需要用到外设的重映射功能时需要使能 AFIO 的时钟。

关于重映射功能举例如下:

1
2
3
4
重映射USART2
USART2的TX/RX在PA.2/3
PA.2已经被Timer2的channel3使用
需要把USART2的TX/RX重映射到PD.5/6

库函数的调用步骤如下:

  1. 使能被重新映射到的I/O端口时钟

    1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);

  2. 使能被重新映射的外设时钟

    1
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

  3. 使能AFIO功能的时钟(勿忘!)

    1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

  4. 进行重映射

    1
    GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);

初始化步骤

  1. 使用 RCC_APB2PeriphClockCmd 函数使能相关 APB2 外设时钟
  2. 通过 GPIO_Init 函数初始化并配置各IO口
  3. 使用 GPIO_ReadInputDataBit 读取IO口电平状态,使用 GPIO_SetBitsGPIO_ResetBits 来设置各IO口的值 (实际我们使用 PAout(0)=1, PBout(2)=0; 这种写法去设置 IO 口的电平状态)

相关函数定义和说明如下:

1. GPIO_Init 初始化函数: 配置IO口的模式和速度

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
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

typedef enum {
GPIOA,
GPIOB,
GPIOC
} GPIO_TypeDef;

typedef struct {
unit16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
} GPIO_InitTypeDef;

typedef enum {
GPIO_Mode_AIN = 0x0, // 模拟输入
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入
GPIO_Mode_IPD = 0x28, // 下拉输入
GPIO_Mode_IPU = 0x48, // 上拉输入
GPIO_Mode_Out_OD = 0x14, // 开漏输出
GPIO_Mode_Out_PP = 0x10, // 通用推挽输出
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出
} GPIOMode_TypeDef;

typedef enum {
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
} GPIOSpeed_TypeDef;

示例:

1
2
3
4
5
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // PB5 端口设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 速度 50MHz
GPIO_Init(GPIOB, &GPIO_InitSture); // 根据参数配置IO口 PB5

2. GIPO_ReadInputDataBit: 读取IO口的电平状态

1
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, unit16_t GPIO_Pin);

示例:

1
2
// 返回值是 1(Bit_SET) 或者 0(Bit_RESET)
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1); // 读取IO口 PA1 的电平状态

3. GPIO_Write: 一次性设置GPIO的多个端口值(通过ODR寄存器实现)

1
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

4. 通过 GPIO_SetBits 和 GPIO_ResetBits 来操作各IO口的值

1
2
void GPIO_SetBits(GPIO_TypeDef* GPIOx, unit16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, unit16_t GPIO_Pin);

示例:

1
2
GPIO_SetBits(GPIOC, GPIO_Pin_3); // 设置IO口 PC3 输出 1
GPIO_ReSetBits(GPIOC, GPIO_Pin_3); // 设置IO口 PC3 输出 0

5. 通过 RCC_APB2PeriphClockCmd 使能IO时钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* RCC_APB2Periph_AFIO 功能复用IO时钟
* 需要用到外设的重映射功能时才需要使能AFIO时钟
*
* RCC_APB2Periph_GPIOA GPIOA时钟
* RCC_APB2Periph_GPIOB GPIOB时钟
* RCC_APB2Periph_GPIOC GPIOC时钟
* RCC_APB2Periph_USART1 USART1时钟(串口1时钟)PA9(TX)、PA10(RX)
*/
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
typedef enum {
Enable = 1,
Disable = 0
} FunctionalState;

参考链接

  1. STM32端口复用和重映射
  2. 什么情况下需要使能AFIO的时钟呢?

SSD1306 8080并口读写过程

  1. 先根据要写入/读取的数据类型,设置 RS 为1(数据)/0(命令)
1
OLED_RS = cmd; // cmd = 0(命令)/1(数据)
  1. 然后拉低片选 CS
1
OLED_CS = 0;
  1. 接着我们根据要读数据还是要写数据置RD/WR为低,然后
    • 读数据:在RD上升沿,读取数据线上的数据
    • 写数据:在WR上升沿,使数据写入到驱动IC里面
1
2
3
4
5
6
// 读数据
OLED_RD = 0;
OLED_RD = 1;
// 写数据
OLED_WR = 0;
OLED_WR = 1;

参考资料

  1. SSD1306 官方手册
  2. Proteus仿真库中英文对照

Keil uVision5+Proteus STM32 开发仿真调试一条龙服务

调试技巧

Source Code Debugging (源码联调)

首先 Keil 端,输出二进制文件格式选择 elf 格式,而不是 hex 或者 axf (axf 其实就是 elf 格式,但是后缀名不一样),因为 elf 格式的二进制文件是带有调试信息的,包含C代码和编译后的汇编代码的映射。这样我们可以直接在更具有可读性的 C 代码上打断点和调试,而不是直接怼汇编代码。

生成好 elf 文件后,我们打开 Proteus 双击 CMU 导入 elf 文件。点击 Start VSM Debugging 可以直接在 C 代码中打断点单步调试。在进入调试模式后,点击菜单栏 Debug 按钮,弹出的列表最下面几项会显示原理图中主要元件的调试选项,如 CM3 -> Source Code - U1 可以进入源代码调试、CM3 -> Registers - U1 可以查看 Cortex-M3 处理器的各个寄存器的状态、SSD1306 LCD Controller RAM - LCD 可以查看 LCD 控制器 SSD1306 内存状态。

Design Explorer

画好原理图后,如果不确定连线是不是都连接上了,可以打开 Design Explorer 视图查看元件各个端口的网络标号 (Net),以此判断是否已连接上。

Probes (探针)

其次,我们可以使用 Proteus 提供的 Probes (探针) 元件 (在左侧工具栏中可以找到),给待测试的线路标上电压探针会电流探针来方便我们判断电路状态。

Terminal Voltages & Terminal Logic States

除了使用探针,在 Proteus 进入单步调试后,我们可以点击各个元件,此时会显示该元件的各个端口电压 (Terminal Voltages) 和各个端口逻辑状态 (Terminal Logic States),也可以方便我们调试。

其中,Proteus 逻辑状态表如下:

逻辑状态 说明 数字逻辑
FLT 悬空态,高阻态 -
WUD 未定义态,与模拟电压混联 -
CON 竞争态,与数字电压冲突 -
SHI 主动输出高逻辑 1
SLO 强电低态,主动输出低逻辑 0
PHI 电源高态,电源高逻辑 1
PLO 电源低态,电源低逻辑 0
WHI 弱电高态,被动输出高逻辑 1
WLO 弱电低态,被动输出低逻辑 0

(非常好用) Diagnostics Configuration

Proteus 还提供一个杀手级的功能,Configure Diagnostics 可以在 Debug 菜单列表中找到或选中元件后右键菜单中找到。通过这个功能,我们可以配置各个元件在日志中输出各种事件,比如我们需要查看 UG-2864HSWEG01 (LCD, SSD1306 Controller included) 关于 Contoller DiagnosticsTrace Information,我们可以把这个 Diagnostics 的 Trace Information Level 改成 Debug 级别。这样我们就可以根据这些事件有没有发生,来判断我们的代码有没有生效。

如通过下面这条日志,我们可以知道 SSD1306 此时使用的是 8080 并行接口的连接方式:

Animation Circuits Configuration

进入 System -> Set Animation Options,Proteus 默认只启用了显示引脚的逻辑状态动画,进入该项设置页后,我们可以把:

  • Show Voltage & Current on Probes 在探针上显示电压和电流
  • Show Logic State of Pins 在引脚上显示逻辑状态 (默认开启)
  • Show Wire Voltage by Color 用颜色标注导线的电压
  • Show Wire Current With Arrows 在导线上标注电流的方向

这些设置项全部开启,这些动画信息可以有效地帮助我们进行调试。

问题排查思路

如果使用了上诉调试技巧还是找不到问题所在,但是日志中显示 proteus simulation is not running in real time due to excessive cpu load 警告,提示你 由于 CPU 超负荷了 Proteus 的仿真不能实时进行。这个警告⚠️很有可能是导致仿真失败、无法复现在真实硬件上的效果的真正原因,建议解决方案如下:

  • 调低 CMU 的晶振频率,如 STM32F103RCT6 的实际晶振频率为 72M,你可以根据你电脑性能的实际情况调整至 比如 1M。双击 CMU 后在 Crystal Frequency 输入框中填写 1M 即可
  • Proteus 进入 System -> Set Simulation Options 调低仿真精度,但是作用其实有限
  • 打开 任务管理器,选中 Proteus 进程右键 转置详细信息,选中进程 PDS.EXE 右键设置设置优先级 实时。这样可以让该进程可以分到更多的 CPU 计算资源,但是作用有限
  • 换一台高性能的设备 /狗头

STM32F103各型号对比

STM32F 系列属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3,STM32F103 则是其中一个子系列。

STM32F103 系列 MCU (微控制器,Microcontroller Unit) 统一采用 ARM Cortex-M3 内核,CPU 最高速度 72 MHz。该系列产品的闪存 Flash 大小由 16KB ~ 1MB 不等,支持多种控制外设、USB全速接口以及 CAN 总线接口

STM32F103 各型号含义如下

STM32F103 引脚数 Flash闪存容量 封装工艺 温度
T: 36 Pin 4: 16KB H: BGA 6: Industrial Temperature range -40℃ to -85℃
C: 48 Pin 6: 32KB T: LQFP 7: Industrial Temperature range -40℃ to -105℃
R: 64 Pin 8: 64KB H: WLCSP64
V: 100 Pin B: 128KB
Z: 144 Pin C: 256KB
D: 384KB
E: 512KB
F: 768KB
G: 1MB

例如,某 MiniSTM32F103开发板V3版本,使用 STM32F103RCT6 作为 MCU。那么我们可以判断出这款 MCU 的参数如下:48 Pin,256KB FLASH,使用 LQFP 工艺封装,工作温度在 -40℃ ~ -85℃ 之间。

更完整分类,请参考官方选型手册:

参考资料

  1. 关于 STM32 软硬件兼容性相关的知识

OpenGauss解锁Locked的账号

在开发 OpenGauss 数据库驱动时,使用同一账户重复登录的次数过多,会导致账户被锁定。

这时候,我们可以使用 gsql 命令行工具,登录另一个管理员账户给这个账号解封:

1
alter user lolimay account unlock;

参考资料

  1. openGauss1.0.0 用户被锁

把十进制表示的字节数组转换为十六进制表示

1
2
3
4
5
6
7
8
9
const arr = '150 82 45 39 181 90 66 61 68 125 221 223 184 235 226 20 52 152 174 14 132 180 221 206 15 10 219 141 96 224 148 78'

const hexBytesToIntArray = bytes => {
return bytes
.map(s => parseInt(s)) // 以 10 进制解析字符串为数字
.map(n => n.toString(16).padStart(4, '0x0')) // 将转为 16 进制表示填充前置0
}

hexBytesToIntArray(arr.split(' '))

需要注意的是,不能把 parseInt 直接作为参数传进 map 中,因为 map 的回调函数签名为 (data: any, index?: number) => any,而 parseInt 的函数签名为 (str: string, radix?: number) => number,如果直接把 parseInt 则默认会把 index 作为 radix (进制)传给 parseInt 导致解析错误。

另,上面提到 parseInt 可以接受一个可选进制 radix 参数,所以把上述方法反过来,也可以完成十六进制表示的数据转换为10进制数据,如:

1
parseInt('0xff', 16).toString(10) // 255

清空当前工作区

之前切换到其它分支时,但是碰到当前工作区有脏修改的情况一直是用的

1
git stash

来把工作区的修改保存在暂存区。其实这种做法是不太优雅的,因为我的目的是丢弃当前工作区的修改。今天特地 Google 了一下,发现了丢弃当前工作区修改的正确方式是:

1
git checkout -- .

别再用nvm了,用fnm!

nvm 做 Node 版本管理器已经有好几年时间了。nvm 说实话是真的慢,每次我开一个新 tmux session,至少都要等3秒以上才能进入会话。慢点没关系,主要功能正常就行,但是最近又出现 nvm 与 prefix 不兼容的问题。

这次打算彻底替代掉它了,Google 了一下,发现社区又造新轮子了 fnm

下载体验了下,rust 写的命令行工具就是快,切换新 session 用时不到一秒!(nvm 是用 shell script 写的,两者高下立判。)

这里贴一下安装方式:

1
curl -fsSL https://fnm.vercel.app/install | bash # 安装

接着添加下面这几行到你的 .zshrc.bashrc 中:

1
2
3
# fnm
export PATH=/Users/lolimay/.fnm:$PATH
eval "`fnm env`"

重启终端即可生效!

Cococs Creator 3.0制作点击回弹效果

给想要添加点击回弹效果的节点添加 cc.Button 组件,然后设置该组件的 Transition 值为 SCALE 即可:

消失的磁盘空间去哪了?

过了几天,磁盘空间终究还是又满了?使用了之前的清理日志的方法只能腾出几百M的空间了。

使用 du 命令查看根目录,发现根目录只占17G,整个磁盘40G,剩下的20多G去哪了呢?

1
2
3
root@lolimay:/# du -sch $(ls --color=never --all) 2> /dev/null | sort -rh
17G total
17G .

通过这篇文章发现可以通过 lsof | grep deleted 命令找到被删除的文件(在文件系统中找不到了),但是由于程序还在运行,导致这个被删除的文件仍被占用,从而占用的磁盘空间不能被释放:

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
root@lolimay:/# lsof | grep deleted | awk '{for(i=1;i<=6;i++){printf "%s ", $i}; print $7/1048576 "MB" " "$8" "$9 }'
nginx 1605 root 2w REG 253,1 0.00390625MB 1454073 /www/wwwlogs/nginx_error.log
nginx 1605 root 4w REG 253,1 0.00390625MB 1454073 /www/wwwlogs/nginx_error.log
nginx 1605 root 5w REG 253,1 0.000277519MB 1861658 /www/server/nginx/off
nginx 1605 root 7w REG 253,1 0.0984421MB 1454074 /www/wwwlogs/api.lolimay.cn.log
nginx 1605 root 8w REG 253,1 0.000199318MB 1454082 /www/wwwlogs/api.lolimay.cn.error.log
nginx 1605 root 9w REG 253,1 0MB 1454083 /www/wwwlogs/csec.lolimay.cn.log
nginx 1605 root 10w REG 253,1 0MB 1454084 /www/wwwlogs/csec.lolimay.cn.error.log
nginx 1605 root 11w REG 253,1 0.00923347MB 1454085 /www/wwwlogs/devtool.lolimay.cn.log
nginx 1605 root 12w REG 253,1 0.0195312MB 1454086 /www/wwwlogs/devtool.lolimay.cn.error.log
nginx 1605 root 13w REG 253,1 0.00899124MB 1454154 /www/wwwlogs/kugou-dev.lolimay.cn.log
nginx 1605 root 14w REG 253,1 0.0195312MB 1454155 /www/wwwlogs/kugou-dev.lolimay.cn.error.log
nginx 1605 root 15w REG 253,1 6.06264MB 1454156 /www/wwwlogs/kugou.lolimay.cn.log
nginx 1605 root 16w REG 253,1 15.3351MB 1454157 /www/wwwlogs/kugou.lolimay.cn.error.log
nginx 1605 root 17w REG 253,1 0.000652313MB 1454158 /www/wwwlogs/kugouv2.lolimay.cn.log
nginx 1605 root 18w REG 253,1 0MB 1454159 /www/wwwlogs/kugouv2.lolimay.cn.error.log
nginx 1605 root 19w REG 253,1 15.8019MB 1454160 /www/wwwlogs/lolimay.cn.log
nginx 1605 root 20w REG 253,1 0.128906MB 1457104 /www/wwwlogs/mmic.lolimay.cn.log
nginx 1605 root 21w REG 253,1 0MB 1457105 /www/wwwlogs/mmic.lolimay.cn.error.log
nginx 1605 root 22w REG 253,1 20948.7MB 1457106 /www/wwwlogs/proxy.lolimay.cn.log <<< 好家伙,20G的空间竟然在这!
nginx 1605 root 23w REG 253,1 147.82MB 1457107 /www/wwwlogs/proxy.lolimay.cn.error.log
nginx 1606 www 2w REG 253,1 0.00390625MB 1454073 /www/wwwlogs/nginx_error.log
nginx 1606 www 4w REG 253,1 0.00390625MB 1454073 /www/wwwlogs/nginx_error.log
nginx 1606 www 5w REG 253,1 0.000277519MB 1861658 /www/server/nginx/off
nginx 1606 www 7w REG 253,1 0.0984421MB 1454074 /www/wwwlogs/api.lolimay.cn.log
nginx 1606 www 8w REG 253,1 0.000199318MB 1454082 /www/wwwlogs/api.lolimay.cn.error.log
nginx 1606 www 9w REG 253,1 0MB 1454083 /www/wwwlogs/csec.lolimay.cn.log
nginx 1606 www 10w REG 253,1 0MB 1454084 /www/wwwlogs/csec.lolimay.cn.error.log
nginx 1606 www 11w REG 253,1 0.00923347MB 1454085 /www/wwwlogs/devtool.lolimay.cn.log
nginx 1606 www 12w REG 253,1 0.0195312MB 1454086 /www/wwwlogs/devtool.lolimay.cn.error.log
nginx 1606 www 13w REG 253,1 0.00899124MB 1454154 /www/wwwlogs/kugou-dev.lolimay.cn.log

我们重启 nginx 进程即可释放这些空间:

1
service nginx restart

再看一下一下磁盘占用:

1
2
3
4
5
6
7
8
root@lolimay:/# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs 3.9G 1.8M 3.9G 1% /dev/shm
tmpfs 3.9G 532K 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
/dev/vda1 40G 17G 21G 45% /
tmpfs 783M 0 783M 0% /run/user/0

舒服了,瞬间有 21G 可用空间了。

为了解除后顾之忧,我们直接在 nginx 的配置文件中把这个站点的日志功能关闭:

1
2
access_log  off;
error_log off;