Lab3:自行车码表

本文详细介绍了基于STM32的自行车码表实验,包括实验准备、点灯、串口输出、按钮检测、下降沿中断、定时器设置以及两种模式的实现。通过PA11和PA12引脚的中断处理,实现了里程和速度的显示功能。同时提供了相关软件和驱动的下载链接以及参考资料。
摘要由CSDN通过智能技术生成

实验准备-点灯

本实验环境为 Mac OS X El Capitan,pd跑 windows 10。

先把下载列表中的软件、驱动都下一遍备用,这时可以喝杯咖啡看看实验攻略 。

强调:东西最好都从官网下,第三方的源在之后有可能掉进各种不知名的坑。

首先安装程序 STM32CubeMX ,打开:
STM32CubeMX

从Help选项卡中的Install New Libraries进入库管理界面,安装对应的库文件。因为在线安装不仅慢而且非常容易断线,所以可以选择左下方的 From Local… 离线安装
From Local

或者将库文件压缩包解压到指定路径。
Lib

然后就可以新建工程,选择核心板的型号STM32F103C8:
STM32F103C8

点击Project选项卡中的setting,进入到项目设置:
Project

为了点亮小灯,将PA9设为输出:
PA9

点击Project-Generate Code:
Generate

如果安装好了 Keil ,就可以用 Keil 打开工程文件了。

这时,如果软件提示你需要安装一个依赖包,那么恭喜你,你多半已经不需要烧录软件,而直接可以用 Keil download了。如果没有也没关系,我们先来写代码让小灯闪烁。软件已经帮我们自动生成了很多代码,我们只需在 main.c 的main函数中添加如下代码:

while (1)
{
  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_9);
  HAL_Delay(100);
}

点击 tool 中最右方的按钮进行配置,先点击Utilities 选项卡
Utilities

中的 Settings 按钮:
settings

如果出现了对应的Programming Algorithm,那么你之后就可以直接按 F8 下板了。如果没有也不要慌,我们先把 Reset and Run 打上勾,这样之后下完板后就不需要手动按板子上的 reset 按钮了。

然后,点击 Debug 选项卡
Debug

右侧的 Settings 按钮:
Settings

如果设备栏里没有报错,说明你已经连上你的板子了。

最后是 Output 选项卡,如果你不能通过 Keil 直接下板,那么就要把 Create HEX File 勾上。
Output

配置完毕,按 F7 编译。如果你可以通过 Keil 直接下板,按 F8 Download。如果不行,那么打开 STM32 ST-LINK Utility 烧录软件:
ST-LINK

找到你的hex文件:
hex

点击 连接开发板 按钮,CTRL-E 擦除芯片,CTRL-P 下载程序。结果如下:
开发板

连接示意图

ST-LINK接四根线3.3V、GND、SWDIO、SWCLK分别对应STM32板子上的3.3V、GND、DIO、DCLK。此为烧录用的线路。而PA9、PA10为串口通信所用的线路。
连接示意图

串口输出

STM32CubeMX 中配置芯片引脚,将PA12、PA11定为输入(接按钮),PA10、PA9分别定为TX、RX(接电脑串口)。
STM32CubeMX

在左侧的配置中,将USART1的模式定为Half-Duplex。
USART1

配置完毕,生成代码。接下来在 Keil 中编辑代码。

stm32f1xx_hal_conf.h 中解除一些宏定义的注释:

#define HAL_MODULE_ENABLED  
#define HAL_GPIO_MODULE_ENABLED 
#define HAL_PWR_MODULE_ENABLED   
#define HAL_TIM_MODULE_ENABLED   
#define HAL_UART_MODULE_ENABLED
#define HAL_USART_MODULE_ENABLED  

main.c 中编写代码:

void UART0_Init(UART_HandleTypeDef* UartHandle){
    UartHandle->Instance = USART1;
    UartHandle->Init.BaudRate = 9600;
    UartHandle->Init.WordLength = UART_WORDLENGTH_8B;
    UartHandle->Init.StopBits = UART_STOPBITS_1;
    UartHandle->Init.Parity = UART_PARITY_NONE;
    UartHandle->Init.HwFlowCtl = UART_HWCONTROL_NONE;
    UartHandle->Init.Mode = UART_MODE_TX_RX;

    HAL_UART_Init(UartHandle);
}

int main(void) {
    ...

    UART_HandleTypeDef UartHandle;
    UART0_Init(&UartHandle);

    HAL_UART_Transmit(&UartHandle, (uint8_t*)"Hello, World!\r\n", 16, 500);

    while (1) {
        ...
    }
}

HAL_UART_Transmit有4个参数,第一个参数是串口的句柄,第二个参数是一个二进制数组(char*),第三个参数是要发送的数据长度,第四个是发送超时的判定时间。

编译下板,可以看到串口的输出:
out1

检测PA11按钮按下

由于按钮接地,所以,当按钮被按下时,PA11应该可以检测到一个低电平的输入。所以检测到PA11引脚值为1时,即为按钮按下。但在实际实验过程中,需要进行按键去抖动。

// ---------- stm32f1xx_hal_msp.c ---------- 
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 


// ---------- main.c ---------- 
#define MASK 0xFF

void anti_jitter(int *bit, int state) {
    *bit <<= 1;
    *bit &= MASK;
    *bit |=state;
}

...

int main(void) {
    ...

    char str[30];
    int Pin_11_Bitcount = 0, Pin_12_Bitcount = 0;
    int Pin_11_State = 0, Pin_12_State = 0;
    int Change_Flag = 1;

    ...

    while (1) {
        GPIO_PinState state_11;
        GPIO_PinState state_12;

        state_11 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11);
        state_12 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_12);
        HAL_Delay(5);
        anti_jitter(&Pin_11_Bitcount,state_11);
        anti_jitter(&Pin_12_Bitcount,state_12);
        if (Pin_11_Bitcount != 0 && Pin_11_State == 0) {
                Pin_11_State = 1;
                Change_Flag = 1;
        }
        if (Pin_11_Bitcount == 0 && Pin_11_State == 1) {
                Pin_11_State = 0;
                Change_Flag = 1;
        }
        if (Change_Flag == 1) {
            Change_Flag = 0;
            if (Pin_11_State == 0)
                HAL_UART_Transmit(&UartHandle, (uint8_t*)"Pressed!\r\n", 16, 500);
        }
    }
}

编译下板,可以看到串口的输出:
out2

PA12下降沿触发中断

PA12引脚的下降沿触发将会触发中断,进入函数EXTI15_10_IRQHandler,此时在函数中调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12)表示查看PA12的值,如果符合条件,则触发HAL_GPIO_EXTI_Callback函数。

// ---------- stm32f1xx_it.c ---------- 
void EXTI15_10_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}

// ---------- main.c ---------- 
int PA12count = 0, PA12flag = 0;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
    if (GPIO_Pin == GPIO_PIN_12){
        PA12flag = 1;
        PA12count ++;
    }else{
        UNUSED(GPIO_Pin);
    }
}

...

int main(void) {
    ...

    while (1) {
        ...

        if (PA12flag == 1) {
            PA12flag = 0;
            cnt = sprintf(str, "Press 12 %d times\r\n", PA12count);
            HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);    
        }
    }
}

// 设置中断优先级
    HAL_NVIC_SetPriority(EXTI15_10_IRQn,0,0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

编译下板,可以看到串口的输出:
out3

定时器

定时器中断的实现思路与引脚涉及的中断基本一致。不同的是需要设置中断触发的时间。不同于外部中断,时钟中断是内部触发,所以需要预先设定好触发时间。

同样的,需要覆写中断触发函数TIM3_IRQHandler,而后在其中对时钟进行判断后触发HAL_TIM_PeriodElapsedCallback。并在callback中真正处理逻辑。

// ---------- stm32f1xx_hal_msp.c ---------- 
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim) {
    __TIM3_CLK_ENABLE();
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htime) {
    __TIM3_CLK_DISABLE();
}

// ---------- main.c ---------- 
TIM_HandleTypeDef TIM_Handle;
int TIMflag = 0, timer = 0;

void TIM3_IRQHandler(void) {
    HAL_TIM_IRQHandler(&TIM_Handle);
}

void TIM_Init() {
    TIM_Handle.Instance = TIM3;
    TIM_Handle.Init.Prescaler = 8000;
    TIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM_Handle.Init.Period = 199;
    HAL_TIM_Base_Init(&TIM_Handle);
    HAL_TIM_Base_Start_IT(&TIM_Handle);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    TIMflag = 1;
    timer++;
}

...

int main(void) {
    ...
    TIM_Init();
    ...

    while (1) {
        ...
        if (TIMflag == 1) {
            TIMflag = 0;
            cnt = sprintf(str, "clicks: %d \r\n", timer);
            HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);
        }
    }
}

// 设置中断优先级
    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);

编译下板,可以看到串口的输出:
out4

自行车码表+PA11中断处理(拓展)

自行车码表程序相当于前两步的综合。这里PA11采用了中断模式,方法跟PA12一样。

附上代码:

// ---------- stm32f1xx_it.c ---------- 
void EXTI15_10_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}

// ---------- stm32f1xx_hal_msp.c ---------- 
        GPIO_InitStruct.Pin = GPIO_PIN_11;
        GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_12;
        GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

...

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim) {
    __TIM3_CLK_ENABLE();
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htime) {
    __TIM3_CLK_DISABLE();
}

// ---------- main.c ---------- 
void UART0_Init(UART_HandleTypeDef* UartHandle) {
    UartHandle->Instance = USART1;
    UartHandle->Init.BaudRate = 9600;
    UartHandle->Init.WordLength = UART_WORDLENGTH_8B;
    UartHandle->Init.StopBits = UART_STOPBITS_1;
    UartHandle->Init.Parity = UART_PARITY_NONE;
    UartHandle->Init.HwFlowCtl = UART_HWCONTROL_NONE;
    UartHandle->Init.Mode = UART_MODE_TX_RX;

    HAL_UART_Init(UartHandle);
}

#define MASK 0xFF

void anti_jitter(int *bit, int state) {
    *bit <<= 1;
    *bit&= MASK;
    *bit|=state;
}

int mode = 0, PA11flag = 0;
int PA12count = 0;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_11) {
        PA11flag = 1;
        mode = 1-mode;
    } else if (GPIO_Pin == GPIO_PIN_12) {
        PA12count ++;
    } else {
        UNUSED(GPIO_Pin);
    }
}

TIM_HandleTypeDef TIM_Handle;
int TIMflag = 0, timer = 0;

void TIM3_IRQHandler(void) {
    HAL_TIM_IRQHandler(&TIM_Handle);
}

void TIM_Init() {
    TIM_Handle.Instance = TIM3;
    TIM_Handle.Init.Prescaler = 8000;
    TIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM_Handle.Init.Period = 199;
    HAL_TIM_Base_Init(&TIM_Handle);
    HAL_TIM_Base_Start_IT(&TIM_Handle);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    TIMflag = 1;
    timer++;
}

int main(void) {
    ...

    char str[30];
    int Pin_11_Bitcount = 0,Pin_12_Bitcount=0;
    int Pin_11_State=0,Pin_12_State=0;
    int Change_Flag=1;
    UART_HandleTypeDef UartHandle;
    UART0_Init(&UartHandle);
    TIM_Init();

    HAL_UART_Transmit(&UartHandle, (uint8_t*)"Hello, World!\r\n", 16, 500);

    while (1) {
        int cnt;

        GPIO_PinState state_11;
        GPIO_PinState state_12;

        state_11 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11);
        state_12 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_12);
        HAL_Delay(5);
        anti_jitter(&Pin_11_Bitcount,state_11);
        anti_jitter(&Pin_12_Bitcount,state_12);

        if (PA11flag == 1) {
            PA11flag = 0;
            if (mode == 0) {
                cnt = sprintf(str, "distance mode\r\n");
                HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);
            } else {
                cnt = sprintf(str, "speed mode\r\n");
                HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500); 
            }
        }
        if (TIMflag == 1) {
            TIMflag = 0;
            if (mode == 0)
                cnt = sprintf(str, "distance: %f \r\n", (float)PA12count*3.14);
            else
                cnt = sprintf(str, "speed: %f \r\n", (float)PA12count*31.4/timer);
            HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);
        }
    }
}

// 设置中断优先级
    HAL_NVIC_SetPriority(EXTI15_10_IRQn,0,0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);

编译下板,可以看到串口的输出:

里程模式

out6

速度模式

out7

下载链接
参考资料
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值