自行車碼錶
一、實驗目的
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個週期。
里程模式:
運行結果如下:
速度模式:
運行結果如下: