PRi——自行车码表

自行车码表

一、实验目的

1   理解MCU上电启动过程;
2   掌握使用Cube库来编写STM32裸机程序的方法;
3   掌握使用Cube库来编写GPIO和UART程序的方法;
4   掌握使用Cube库来编写中断响应程序的方法;
5   理解前后台程序模式;
6   掌握在STM32F103上编写裸机程序并下载运行的方法

二、实验内容

1.  画出你所实际实施的连接示意图;
2.  描述所做的实验步骤,给出各步操作的命令和结果;
3.  给出你写的源代码,并加以解释;
4.  给出试用的结果。

三、实验器材

硬件
1   STM32F103核心板板一块;
2   microUSB线一根(供电);
3   STLink板或USB串口板一块。

软件
交叉编译软件。
还需要安装一些其他的软件和补丁等等

四、实验步骤

1、实验准备

    安装STM32CubeMX,直接一步一步安装,打开程序之后界面如下:

这里写图片描述

直接新建工程,选择核心板的型号为STM32F103C8,点击OK。

这里写图片描述

点击 project->settings,进入项目设置。填写完项目名称、位置以及工具链。如下图所示:

这里写图片描述

全部设置后之后,点击下面的OK按钮,会跳出一个警告:

这里写图片描述

点击yes 开启自动下载模式,全自动安装:

这里写图片描述

此外,还可以进行本地安装,选择help -> Install New Libraries 进入库管理界面:

这里写图片描述

安装keil V5,下载安装包直接安装,但是要在注册机上获得LIC。

(2)实验连接图
连接图如下:(是已经全部连接好的连接图,但是在前几步的时候可以不用全部连接好)

这里写图片描述

其中,ST-LINK接四根线3.3V、GND、SWDIO、SWCLK分别对应STM32板子上的3.3V、GND、DIO、DCLK。此为烧录用的线路。而PA9、PA10为串口通信所用的线路。所以图中使用了两个USB口。

面包板上线的连接方式为从引脚出来之后经过按钮到GND。

2、编写Cube程序,配置UART0为9600,8n1,上电后向串口输出“Hello”,在PC上通过串口软件观察结果;

    进入STM32CubeMX,按照实验中的要求,在右侧芯片设置中,将PA12、PA11定为输入(接按钮),PA10、PA9分别定为TX、RX(接电脑串口)。

这里写图片描述

    同时在左侧的配置中,将USART1的模式定为Half-Duplex。这步所对应生成的代码与实验攻略中的代码略有差别。但是在不指定模式的情况下,PA9以及PA10会被认为是GPIO_Output而与PA11一起进行初始化,而不是TX、RX口。所以在此选择一个模式。

这里写图片描述

    配置完成,接下来就是代码生成,点击按钮等待就可以了。

这里写图片描述

    生成结束后,就会在项目的文件夹下面生成一个由设置决定的工程文件夹,直接点击Open Project,打开项目工程文件。中间还会需要安装一个依赖包,继续同意即可。
    现在可以从下面的图中看到,STM32CubeMX帮我们生成好了一系列的基础文件:

这里写图片描述

    代码生成完成之后,基本的函数结构已经生成了。但是还需要自己手动填写一些代码。而stm32f1xx_hal_msp.c中所需填写的函数与Half-Duplex模式一致,所以不需要进行大幅度改动。只需要将攻略的几行代码填入即可。
    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);

    while (1) {
        HAL_UART_Transmit(&UartHandle, (uint8_t*)"hello\r\n", 7, 500);
        HAL_Delay(100);    
    }
}
    HAL_UART_Transmit有4个参数,第一个参数是串口的句柄,第二个参数是一个二进制数组(char*),第三个参数是要发送的数据长度,第四个是发送超时的判定时间。
    代码:
/* MCU Configuaration------*/

/* Reset of all peripherals, Initializes the HAL*/
HAL_Init();

/* Configuaration the system clock  */
SystemClock_Config();

/* Initialize all configured peripherals */
MX_GPIO_Init();

//MX_USART1_UART_Init();

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1){
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_UART_Transmit(&UartHandle, (uint8_t*)"hello\r\n", 7, 500);
    HAL_Delay(100);
}
    代码添加完成之后,在下载之前还需要在Flash ->Configure Flash Tools -> Utilities 中,将Use Debug Driver的勾选去掉,转而使用ST-LINKV2

这里写图片描述

    运行结果如下:

这里写图片描述

3、通过面包板在PA11和PA12各连接一个按钮开关到地;

    这个在之前的实验准备中已经连接好。可以在上面再看一下。

4、编写Cube程序,配置PA11和PA12为内部上拉到输入模式,在main()函数循环检测PA11按钮按下,并在按钮按下时 在串口输出“Pressed”;

    由于按钮接地,所以,再按钮在按下的时候,PA11应该可以检测到一个低电平的输入,编写程序的时候可以根据这一点,读取PA11的脚值。
    在实验过程中,可能由于两次检测时间间隔太短,导致调变的脚值多次被记录下来,即使按键没有按下,串口还是有输出,此时加入一个防抖动函数和延时的函数。即添加anti_jitter和 HAL——Delay,程序如下:
#define MAX_BITCOUNT 0xff
#define CHECK_DELAY 10

void anti_jitter(int *bitcount, int state){
    *bitcount <<= 1;
    *bitcount &= MAX_BITCOUNT;
    *bitcount += state & 1;
}

int main(void)
{
    int total, bitcount;
    char str[64];
    int send = 0;
// something for initialization.
    total = 0; bitcount = MAX_BITCOUNT ;
    while (1) {
        int cnt;
        GPIO_PinState state;
        state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11);
        HAL_Delay(CHECK_DELAY);
        anti_jitter(&bitcount, state);
        if (bitcount == 0){
            if (!send){
                send = 1;
                total++;
                cnt = sprintf(str, "Press 11 %d times\r\n", total);
                HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);
            }
        }else{
            send = 0;
        }
    }
}
最终的话,可以做到无误的检测,代码如下:
/* USER CODE BEGIN 2 */
UART_HandleTypeDef UartHandle;
UART0_init(&UartHandle);

//GPIO_Init();
//LSI_Init();
//PLL_Init()

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
total = 0; bitcount = 0;
while(1){
    int cnt;
/* USER CODE END WHILE */
    GPIO_PinState state;
/* USER CODE BEGIN 3 */
    state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11);
    HAL_Delay(CHECK_DELAY);
    anti_jitter(&bitcount, state);
    if (bitcount == 0){
        if (!send){
            send = 1;
            total++;
            cnt = sprintf(str, "Press 11 %d times\r\n", total);
            HAL_UART_Transmit(&UartHandle, (unit8_t*)str, cnt, 500);
        }
    }else{
        send == 0;
    }
}
运行结果如下(PA11 按钮事件):

这里写图片描述

5、编写Cube程序,配置PA12下降沿触发中断,程序中设置两个全局变量,一个为计数器,一个为标识。当中断触发 时,计数器加1,并设置标识。在主循环中判断标识,如果标识置位则清除标识并通过串口输出计数值;

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

    在callback函数中,将检测标志位置1即可被while循环中的if识别并输出。代码如下:
//stm32f1xx_it.c 
void EXTI15_10_IRQHandler(void){
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}

// main.c 
int PA12count = 0, PA12flag = 0;//在main函数开始的时候定义的
......

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) {
    int total, bitcount;
    char str[64];
    int send = 0;
......
    while (1) {
        int cnt;
......
        if (PA12flag == 1){
            PA12flag = 0;
            cnt = sprintf(str, "Press 12 %d times\r\n", PA12count);
            HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);    
        }
    }
}

void MX_GPIO_Init(void) {
......    
  GPIO_InitStruct.Pin = GPIO_PIN_12;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
......
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
中断响应,代码如下:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
total = 0; bitcount = 0;
while(1){
    int cnt;
    /* USER CODE END WHILE */
    GPIO_PinState state;
/* USER CODE BEGIN 3 */
    state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11);
    HAL_Delay(CHECK_DELAY);
    anti_jitter(&bitcount, state);
    if (bitcount == 0){
        if (!send){
            send = 1;
            total++;
            cnt = sprintf(str, "Press 11 %d times\r\n", total);
            HAL_UART_Transmit(&UartHandle, (unit8_t*)str, cnt, 500);
        }
    }else{
        send == 0;
    }
    if (PA12flag == 1){
        PA12flag = 0;
        cnt = sprintf(str, "Press 12 %d times\r\n", PA12total);
        HAL_UART_Transmit(&UartHandle, (unit8_t*)str, cnt, 500);
    }
}
/* USER CODE END 3 */
运行结果如下:

这里写图片描述

6、编写Cube程序,开启定时器为200ms中断一次,中断触发时设置标识,主循环根据这个标识来做串口输出(取消4 的串口输出);

    定时器中断需要设置中断触发的时间。因为不同于外部中断,时钟中断是内部触发,所以需要预先设定好触发时间。

    而后,需要覆写中断触发函数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;
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;
}

int main(void){
......
    total = 0; bitcount = MAX_BITCOUNT;
    while (1) {
        int cnt;
        GPIO_PinState state;

        HAL_Delay(CHECK_DELAY);
        state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11);
        anti_jitter(&bitcount, state);    
        if (bitcount == 0){
            if (send != 2){
                send = 1;
            }
        }else if (send == 2){
            send = 0;
        }
        if (TIMflag == 1){
            TIMflag = 0;
            if (send == 1){
                send = 2;
                total++;
                cnt = sprintf(str, "Press 11 %d times\r\n", total);
                HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);
            }
            if (PA12flag == 1){
                PA12flag = 0;
                cnt = sprintf(str, "Press 12 %d times\r\n", PA12count);
                HAL_UART_Transmit(&UartHandle, (uint8_t*)str, cnt, 500);            
            }
        }
    }
}
运行结果如下:

这里写图片描述

7、编写完整的码表程序,PA12的按钮表示车轮转了一圈,通过计数器可以得到里程,通过定时器中断得到的时间可以 计算出速度;PA11的按钮切换模式,模式一在串口输出里程,模式二在串口输出速度。

    码表有两个模式,里程模式以及速度模式,两个模式有区别也有联系。

    首先是最基础的里程模式,该模式只对应PA12按钮的事件。在每次PA12被按下(轮子走了一圈)的时候算好里程即可,或者使用圈数*轮子周长的方式也行。在本程序中,将轮子周长表示为3.14m。

    其次是速度模式。速度模式不能单单使用总里程/总时间,这样得到的总速度是没有意义的,而近似实时的速度计算法应该是通过计算在一定时间内所行驶的里程数推算出短时间内的速度。在本程序中,使用1.6s作为计算的时间长度,即8个周期。

里程模式:

这里写图片描述

运行结果如下:

这里写图片描述

速度模式:

这里写图片描述

运行结果如下:

这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章