实验准备-点灯
本实验环境为 Mac OS X El Capitan,pd跑 windows 10。
先把下载列表中的软件、驱动都下一遍备用,这时可以喝杯咖啡看看实验攻略 。
强调:东西最好都从官网下,第三方的源在之后有可能掉进各种不知名的坑。
首先安装程序 STM32CubeMX
,打开:
从Help选项卡中的Install New Libraries进入库管理界面,安装对应的库文件。因为在线安装不仅慢而且非常容易断线,所以可以选择左下方的 From Local…
离线安装
或者将库文件压缩包解压到指定路径。
然后就可以新建工程,选择核心板的型号STM32F103C8:
点击Project选项卡中的setting,进入到项目设置:
为了点亮小灯,将PA9设为输出:
点击Project-Generate Code:
如果安装好了 Keil
,就可以用 Keil
打开工程文件了。
这时,如果软件提示你需要安装一个依赖包,那么恭喜你,你多半已经不需要烧录软件,而直接可以用 Keil
download了。如果没有也没关系,我们先来写代码让小灯闪烁。软件已经帮我们自动生成了很多代码,我们只需在 main.c
的main函数中添加如下代码:
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_9);
HAL_Delay(100);
}
点击 中最右方的按钮进行配置,先点击
Utilities
选项卡
中的 Settings
按钮:
如果出现了对应的Programming Algorithm,那么你之后就可以直接按 F8
下板了。如果没有也不要慌,我们先把 Reset and Run
打上勾,这样之后下完板后就不需要手动按板子上的 reset
按钮了。
然后,点击 Debug
选项卡
右侧的 Settings
按钮:
如果设备栏里没有报错,说明你已经连上你的板子了。
最后是 Output
选项卡,如果你不能通过 Keil
直接下板,那么就要把 Create HEX File
勾上。
配置完毕,按 F7
编译。如果你可以通过 Keil
直接下板,按 F8
Download。如果不行,那么打开 STM32 ST-LINK Utility
烧录软件:
找到你的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(接电脑串口)。
在左侧的配置中,将USART1的模式定为Half-Duplex。
配置完毕,生成代码。接下来在 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*),第三个参数是要发送的数据长度,第四个是发送超时的判定时间。
编译下板,可以看到串口的输出:
检测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);
}
}
}
编译下板,可以看到串口的输出:
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);
编译下板,可以看到串口的输出:
定时器
定时器中断的实现思路与引脚涉及的中断基本一致。不同的是需要设置中断触发的时间。不同于外部中断,时钟中断是内部触发,所以需要预先设定好触发时间。
同样的,需要覆写中断触发函数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);
编译下板,可以看到串口的输出:
自行车码表+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);
编译下板,可以看到串口的输出: