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

部分原理图展示

LED部分
KEY部分

按键监控点亮LED灯

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

其他函数

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);
    }
}

主函数

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 0~4的写法基本一致:EXTI0_IRQn~EXTI4_IRQn,但Line 5~9和Line 10~15写为EXTI9_5_IRQnEXTI15_10_IRQn.
宏部分

#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

核心函数

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); // 略去

主函数

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执行后面的内容。

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;
}

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

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站视频底下看到的,还未测试。

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转串口

宏部分

#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

核心函数

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: 发送完成
}

中断服务函数

void USART_IRQHandler(void){
    uint8_t data;
    if (USART_GetFlagStatus(USARTx, USART_IT_RXNE) != RESET){
        data = USART_ReceiveData(USARTx);
        USART_SendData(USARTx, data);
    }
}

主函数

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()中的中断相关部分

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

重写fputc/fgetc

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);
}

主函数

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通道请求时,由仲裁器管理响应处理的顺序。

宏部分

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

核心函数

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);
}

主函数

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历史版本的几种下载方法