这里用的是野火霸道的板子,搭载了STM32F103ZET6. 目前所学知识尚且较少,这里的笔记仅仅记录一下学习过程中的代码,方便以后回头复习以及复制。目前来说,似乎大部分代码都非常易懂,故注释较少。参考资料——《零死角玩转 STM32F103—霸道_V2 开发板》,参考视频——150集-野火F103霸道/指南者视频教程

部分原理图展示

LED部分
KEY部分

按键监控点亮LED灯

利用GPIO监控按键电位,从而控制LED灯亮灭。GPIO模式区别

其他函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Delay(__IO uint32_t nCount){
for(; nCount != 0; nCount--);
}

uint8_t waitkey(GPIO_TypeDef* GPIOx, uint16_t GPIO_PIN){
if (GPIO_ReadInputDataBit(GPIOx, GPIO_PIN) == 1){
while(GPIO_ReadInputDataBit(GPIOx, GPIO_PIN) == 1);
return 1;
}else{
return 0;
}
}

void LED_TURN_ON(char i){
GPIO_SetBits(GPIOB, GPIO_Pin_All);
if (i % 3 == 0){
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
}else if(i % 3 == 1){
GPIO_ResetBits(GPIOB, GPIO_Pin_0);
}else if(i % 3 == 2){
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}
}

主函数

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
int main(void){
uint16_t COUNT = 0;
// 初始化
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_All);

// 监测按键
while (1){
if (waitkey(GPIOA, GPIO_Pin_0) == 1){
GPIO_SetBits(GPIOB, GPIO_Pin_All);
}
if (waitkey(GPIOC, GPIO_Pin_13) == 1){
LED_TURN_ON(COUNT++);
}
}
}

中断

中断函数的名称可以在启动文件内接近尾端部分查到。关于Interrupt Numbers,External Line 04的写法基本一致:EXTI0_IRQnEXTI4_IRQn,但Line 59和Line 1015写为EXTI9_5_IRQnEXTI15_10_IRQn.
宏部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define KEY1_GPIO_CLK		RCC_APB2Periph_GPIOA
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_PIN GPIO_Pin_0
#define KEY1_EXTI_PORTSRC GPIO_PortSourceGPIOA
#define KEY1_EXTI_PINSRC GPIO_PinSource0
#define KEY1_EXTI_LINE EXTI_Line0
#define KEY1_EXTI_IRQ EXTI0_IRQn
#define KEY1_IRQHandler EXTI0_IRQHandler
// EXTI0_IRQHandler/EXTI15_10_IRQHandler 在.s文件内部

#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOC
#define KEY2_GPIO_PIN GPIO_Pin_13
#define KEY2_GPIO_PORT GPIOC
#define KEY2_EXTI_PORTSRC GPIO_PortSourceGPIOC
#define KEY2_EXTI_PINSRC GPIO_PinSource13
#define KEY2_EXTI_LINE EXTI_Line13
#define KEY2_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler

#define LED_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED_GPIO_PORT GPIOB
#define LED_R_GPIO_PIN GPIO_Pin_5
#define LED_G_GPIO_PIN GPIO_Pin_0
#define LED_B_GPIO_PIN GPIO_Pin_1

核心函数

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
static void EXTI_NVIC_Config(void){ // 内核中的相关函数在misc.c内可查
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStruct.NVIC_IRQChannel = KEY1_EXTI_IRQ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = KEY2_EXTI_IRQ;
NVIC_Init(&NVIC_InitStruct);
}

void EXTI_Key_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;

RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 可直接写成 KEY1_GPIO_CLK | RCC_APB2Periph_AFIO
EXTI_NVIC_Config();

// 配置KEY1
GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);
// EXTI初始化
GPIO_EXTILineConfig(KEY1_EXTI_PORTSRC, KEY1_EXTI_PINSRC);
EXTI_InitStruct.EXTI_Line = KEY1_EXTI_LINE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // event & interrupt
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发 低->高电平 按下触发
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
// 配置KEY2
GPIO_InitStruct.GPIO_Pin = KEY2_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStruct);
// EXTI初始化
GPIO_EXTILineConfig(KEY2_EXTI_PORTSRC, KEY2_EXTI_PINSRC);
EXTI_InitStruct.EXTI_Line = KEY2_EXTI_LINE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发 高->低电平 弹开触发
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
}

void KEY1_IRQHandler(void){ // void EXTI0_IRQHandler()
//确保是否产生了 EXTI Line 中断
if (EXTI_GetITStatus(KEY1_EXTI_LINE) != RESET) { // 即为set
count--;
LED_TURN_ON(count);
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_EXTI_LINE);
}
}

void KEY2_IRQHandler(void){ // void EXTI10_15_IRQHandler()
//确保是否产生了 EXTI Line 中断
if (EXTI_GetITStatus(KEY2_EXTI_LINE) != RESET) {
count++;
LED_TURN_ON(count);
//清除中断标志位
EXTI_ClearITPendingBit(KEY2_EXTI_LINE);
}
}

void LED_init(void){
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = LED_R_GPIO_PIN;
GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = LED_G_GPIO_PIN;
GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = LED_B_GPIO_PIN;
GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
GPIO_SetBits(LED_GPIO_PORT, GPIO_Pin_All);
}

void LED_TURN_ON(char i); // 略去

主函数

1
2
3
4
5
6
char count = 0;
int main(void){
LED_init();
EXTI_Key_Config();
while(1){}
}

SysTick

24bit递减计数器,每计数一次用时为1/SYSCLK,一般SYSCLK等于72M。SysTick属于内核外设,有关的寄存器定义和库函数都在内核相关的库文件core_cm3.h中。SysTick属于内核外设,跟普通外设的中断优先级有区别,没有抢占优先级和子优先级的说法,其优先级只需要配置一个寄存器即可,取值范围为0000b~1111b。但是中断优先级分组依旧对其起作用,即配置优先级方式不同,但优先级划分方式还是跟分组的一致。
下面这一段代码在我的机器上似乎有点问题,一执行就会卡死。试过在while循环下面添加点亮LED,但是始终没有成功点亮,说明没有跳出循环,这貌似是CRTL寄存器位16始终没有置1。尝试扩展第6行,在while循环体中输出SysTick->VAL,但是只会输出两次VAL的值,然而并未跳出Delay_us执行后面的内容。

1
2
3
4
5
6
7
8
9
10
void Delay_us( __IO uint32_t us){
uint32_t i;
SysTick_Config(SystemCoreClock/1000000);
for (i=0; i<us; i++) {
// 计数器值减小到0时,CRTL寄存器位16会置1
while ( !((SysTick->CTRL)&(1<<16)) );
}
// 关闭 SysTick 定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

另一种方式,这种无问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static __IO u32 TimingDelay;

void Delay_ms(__IO u32 nTime){
SysTick_Config(SystemCoreClock / 1000); // 72M -> 1s 72k -> 1ms 72 -> 1us
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭
TimingDelay = nTime; // 置数
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 开启
while(TimingDelay != 0);
}

void SysTick_Handler(void){ // TimingDelay_Decrement
if (TimingDelay != 0)
TimingDelay--;
else
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭定时器
}

还有一种方式,在B站视频底下看到的,还未测试。

1
2
3
4
5
6
7
8
9
10
11
void delay_us(uint32_t nus){
uint32_t temp;
SysTick->LOAD=9*nus;
SysTick->VAL=0x00; //清空计算器
SysTick->CTRL=0x01; //使能系统计算器,减到零时无动作,采用外部时钟源,并8分频
do{
temp=SysTick->CTRL; //读取当前就算器控制寄存器值
}while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL=0x00; //清空计数器
}

USART收发数据 (中断式)

同步异步收发器 (Universal Synchronous Asynchronous Receiver and Transmitter, USART)

USB转串口部分原理图
USB转串口

宏部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define USARTx			USART1
#define USART_CLK RCC_APB2Periph_USART1
#define USART_CLK_Cmd RCC_APB2PeriphClockCmd
#define USART_BAUDRATE 115200

#define USART_GPIO_CLK RCC_APB2Periph_GPIOA
#define USART_GPIO_CLK_Cmd RCC_APB2PeriphClockCmd

#define USART_TX_GPIOx GPIOA
#define USART_TX_GPIO_PIN GPIO_Pin_9
#define USART_RX_GPIOx GPIOA
#define USART_RX_GPIO_PIN GPIO_Pin_10

#define USART_IRQ USART1_IRQn
#define USART_IRQHandler USART1_IRQHandler

核心函数

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
static void NVIC_Config(void){ // 接收的时候才需要中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = USART_IRQ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}

void USART_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
USART_GPIO_CLK_Cmd(USART_GPIO_CLK, ENABLE); // 打开串口的GPIO时钟
USART_CLK_Cmd(USART_CLK, ENABLE); // 打开串口外设时钟
// USART TX GPIO 配置
GPIO_InitStruct.GPIO_Pin = USART_TX_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(USART_TX_GPIOx, &GPIO_InitStruct);
// USART RX GPIO 配置
GPIO_InitStruct.GPIO_Pin = USART_RX_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 接收端设置浮空
GPIO_Init(USART_RX_GPIOx, &GPIO_InitStruct);
// 串口配置
USART_InitStruct.USART_BaudRate = USART_BAUDRATE; // 波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 字长
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 停止位
USART_InitStruct.USART_Parity = USART_Parity_No; // 校验位
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 工作模式 - 收发
USART_Init(USARTx, &USART_InitStruct);
NVIC_Config(); // 串口中断优先级设置
USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE); // 使能接收中断
USART_Cmd(USARTx, ENABLE); // 使能串口
}

void USART_Send_Byte(USART_TypeDef* pUSARTx, uint8_t data){
USART_SendData(pUSARTx, data);
while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET ); // USART_FLAG_TXE: 发送数据寄存器为空
}

void USART_Send_Str( USART_TypeDef * pUSARTx, char *str){
uint32_t k = 0;
while ( *(str+k) != '\0' ){
USART_Send_Byte(pUSARTx, *(str+(k++)) );
}
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET); // USART_FLAG_TC: 发送完成
}

中断服务函数

1
2
3
4
5
6
7
void USART_IRQHandler(void){
uint8_t data;
if (USART_GetFlagStatus(USARTx, USART_IT_RXNE) != RESET){
data = USART_ReceiveData(USARTx);
USART_SendData(USARTx, data);
}
}

主函数

1
2
3
4
5
int main(void){
USART_Config();
USART_Send_Str(USARTx, "Hello World");
while(1);
}

Note: 有BUG,无法接收数据,原因尚不清楚,只知道中断服务函数并未调用。 主函数最后加上while(1);即可,不加的话,貌似会进入HARDFAULT,导致中断服务函数无法执行。

USART 通信控制LED (标准库)

头部记得包含#include <stdio.h>。这一部分为上一部分略作修改。
关闭USART_Config()中的中断相关部分

1
2
3
4
...
// NVIC_Config(); // 串口中断优先级设置
// USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE); // 使能接收中断
...

重写fputc/fgetc

1
2
3
4
5
6
7
8
9
10
int fputc(int ch, FILE *f){
USART_SendData(USARTx, (uint8_t)ch);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
return ch;
}

int fgetc(FILE *f){
while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);
return (int32_t) USART_ReceiveData(USARTx);
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(void){
char show;
LED_init();
USART_Config();
printf("Hello world\n");
while(1){
show = getchar();
printf("#: %c\n", show);
switch (show){
case 'r':
LED_TURN_ON(0);
break;
case 'g':
LED_TURN_ON(1);
break;
case 'b':
LED_TURN_ON(2);
break;
default:
printf("undefined.\n");
}
}
}

DMA+USART发送数据

DMA控制器独立于内核,属于一个单独的外设,主要是用来搬数据,且不占用CPU。不同的DMA控制器的通道对应着不同的外设请求, 应当根据DMA请求映像表选择通道。当发生多个DMA通道请求时,由仲裁器管理响应处理的顺序。

宏部分

1
2
3
4
// 定义DMA请求通道、外设数据寄存器地址以及Buffsize
#define USART_TX_DMA_CHAN DMA1_Channel4
#define USART_DR_ADDR (u32) &USART1->DR // USART1_BASE+0x04
#define BUFFER_SIZE 1024

核心函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uint32_t send_buff[BUFFER_SIZE];

void USARTx_DMA_Config(void){
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStruct.DMA_PeripheralBaseAddr = USART_DR_ADDR;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)send_buff;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST; // 外设为接收端
DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设寄存器只有一个,不递增地址
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增
DMA_InitStruct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; // 外设数据单位
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; // Normal只发送一次,设置Circular可以循环发送
DMA_InitStruct.DMA_Priority = DMA_Priority_High; // 优先级
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; // 非Memory 2 Memory传输
DMA_Init(USART_TX_DMA_CHAN, &DMA_InitStruct);
DMA_Cmd(USART_TX_DMA_CHAN, ENABLE);
}

主函数

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
int main(void){
uint32_t i;
LED_init();
USART_Config();
USARTx_DMA_Config();
for(i = 0; i < BUFFER_SIZE; i++){
send_buff[i] = 'a';
}
USART_DMACmd(USARTx, USART_DMAReq_Tx, ENABLE); // 使能 USARTx向DMA发出TX请求
LED_TURN_ON(0);
Delay_ms(500);
LED_TURN_ON(1);
Delay_ms(500);
for(i = 0; i < BUFFER_SIZE; i++){
send_buff[i] = 'b';
}
USART_DMACmd(USARTx, USART_DMAReq_Tx, DISABLE);
DMA_DeInit(USART_TX_DMA_CHAN);
USARTx_DMA_Config();
USART_DMACmd(USARTx, USART_DMAReq_Tx, ENABLE);
LED_TURN_ON(0);
Delay_ms(500);
LED_TURN_ON(1);
Delay_ms(500);
}

其他

最初用keil5的时候就出了一堆问题,然后换keil4用了一段时间。感觉keil4的代码补全有点问题,于是我想到要用vscode配合Keil-Assistant插件来敲代码,可惜Keil-Assistant只支持Keil μVison 5及以上版本,我用新的Keil5可以正常编译,但是死活无法烧录,烧录时IDE只会输出Error: Flash Download failed - "Cortext-M3"。根据百度的结果,以及问了好几位大哥,都无法解决问题,最后我换了MDK520版本

关于MDK与C51共存

参考链接:
MDK和各种pack软件包镜像下载
Keil历史版本的几种下载方法