文章目錄
- 前言
- 一、外部中斷按鍵消抖
- 1、工程配置
- 2、工程代碼
- 二、 Usart與Printf函式重定向
- 1、工程配置
- 2、工程代碼
- 三、STM32 Uart 接收資料
- 1、工程代碼
- 四、定時器編碼器模式讀取脈沖資料
- 1、工程配置
- 2、工程代碼
- 五、PWM與TB6612FNG驅動電機
- 1、工程配置
- 2、工程代碼
- 遇到的問題
- 總結
前言
上周講到的消除按鍵抖動代碼用的是阻塞式等待,
這次用第二種方式——非阻塞等待,就是判斷當前uwTick與基準uwTick之間的差值是否大于目標“閾值”,使用這種結構相比較阻塞式等待能大幅提升效率
所用工具:
1、芯片: STM32F103C8T6
2、STM32CubeMx軟體
3、IDE: MDK-Keil軟體
4、STM32F1xx/STM32F4xxHAL庫
提示:以下是本篇文章正文內容,下面案例可供參考
一、外部中斷按鍵消抖
1、工程配置
- 1、設定RCC

- 2、設定串口
1、設定按鍵PA8為GPIO_EXTI8
2、設定GPIO mode 為 External lnterrupt Mode with Falling edge trigger detection(具有下降沿觸發檢測的外部中斷模式)
3、設定GPIO Pull-up

1、設定按鍵PB12為GPIO_Output
2、設定GPIO mode 為 Output Push Pull(輸出推拉)
3、設定GPIO NO pull-up and no pull-down
4、設定Maximum output speed為High

- 3、打開中斷
點擊NVIC打開對應的Enabled按鈕

- 4、創建工程
點擊 GENERATE CODE,重新生成代碼,
2、工程代碼
點開工程的stm32f1xx_it.c可以看到下面的代碼
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
/* USER CODE BEGIN EXTI9_5_IRQn 1 */
/* USER CODE END EXTI9_5_IRQn 1 */
}
右鍵 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8)
點擊GO to Definition
可以看見下面的代碼
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
復制void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)到main.c的下面USER CODE BEGIN 4 和 USER CODE END 4 之間,然后加入下面的代碼,代碼中的HAL_GetTick() 也可以替換為 uwTick
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static uint32_t TickBase=0;
if(GPIO_Pin==Button_Pin && HAL_GetTick() - TickBase >= 30)
{
TickBase = HAL_GetTick();
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
}
}
二、 Usart與Printf函式重定向
1、工程配置
- 1、設定
在 Pinout&Configuration 界面中點擊左側 Connectivity 選擇 USART1,然后在 USART1 Mode and Configuration 的 Mode 中選擇 Asynchronous,Asynchronous 為異步通信的意思,Parameter Setting(基礎設定)為默認設定,其中波特率為 115200 Bits/s,位元組長度為 8 Bits,

- 2、開啟中斷
- 選擇 NVIC Setting,將Enable打鉤 ,啟用 USART1 中斷,

- 3、創建工程
點擊 GENERATE CODE,重新生成代碼,
2、工程代碼
在 MDK-ARM 工程里要把 USE MicroLIB 選上

打開 usart.c,在 /* USER CODE BEGIN 0 / 和 / USER CODE END 0 */ 把標準輸入輸入頭檔案 stdio.h 加進去,并且加入以下代碼
#include "stdio.h"
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

繼續在 /* USER CODE BEGIN 1 / 和 / USER CODE END 1 */ 之間加入以下代碼:
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}

至此,我們已經完成了Printf函式的重定向,之后的代碼里面就可以直接使用Print函式了
三、STM32 Uart 接收資料
1、工程代碼
打開usart.c,在 /* USER CODE BEGIN 0 / 和 / USER CODE END 0 */ 之間加入以下代碼:
uint8_t uart1Rx[1024];//申請1024個位元組的陣列
uint8_t *pUart1Rx =uart1Rx;//定義一個指標,用來指向存放資料的陣列
uint16_t uart1Size;//接收到資料的長度
在 stm32f1xx_it.c 中右鍵 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
GO to Definition
在HAL_UART_IRQHandler() 函式里加上這一段,
/* UART in mode Idle -------------------------------------------------*/
if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
{
HAL_UART_IdleCpltCallback(huart);
return;
}
然后在HAL_UART_IRQHandler() 函式外面加上這段
__weak void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
}
至此,整個 HAL_UART_IRQHandler() 函式,是這樣的,
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
}
/* If some errors occur */
if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
{
/* UART parity error interrupt occurred ----------------------------------*/
if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_PE;
}
/* UART noise error interrupt occurred -----------------------------------*/
if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_NE;
}
/* UART frame error interrupt occurred -----------------------------------*/
if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_FE;
}
/* UART Over-Run interrupt occurred --------------------------------------*/
if (((isrflags & USART_SR_ORE) != RESET) && (((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_EIE) != RESET)))
{
huart->ErrorCode |= HAL_UART_ERROR_ORE;
}
/* Call UART Error Call back function if need be --------------------------*/
if (huart->ErrorCode != HAL_UART_ERROR_NONE)
{
/* UART in mode Receiver -----------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
}
/* If Overrun error occurs, or if any error occurs in DMA mode reception,
consider error as blocking */
dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
{
/* Blocking error : transfer is aborted
Set the UART state ready to be able to start again the process,
Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
UART_EndRxTransfer(huart);
/* Disable the UART DMA Rx request if enabled */
if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
/* Set the UART DMA Abort callback :
will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
{
/* Call Directly XferAbortCallback function in case of error */
huart->hdmarx->XferAbortCallback(huart->hdmarx);
}
}
else
{
/* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
else
{
/* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
else
{
/* Non Blocking error : transfer could go on.
Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
huart->ErrorCode = HAL_UART_ERROR_NONE;
}
}
return;
} /* End if some error occurs */
/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
/* UART in mode Transmitter end --------------------------------------------*/
if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
UART_EndTransmit_IT(huart);
return;
}
}
開啟中斷,將 usart.c 中MX_USART1_UART_Init(void) 函式的
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
改寫成
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
else
{
HAL_UART_Receive_IT(&huart1,pUart1Rx,1);
}
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
然后在 /* USER CODE BEGIN 1 / 和 / USER CODE END 1 */ 之間加入以下代碼:
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
pUart1Rx++;
uart1Size++;
//HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
HAL_UART_Receive_IT(&huart1,pUart1Rx,1);
}
}
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
if(huart == &huart1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
HAL_UART_Transmit(&huart1,uart1Rx,uart1Size,1000);
pUart1Rx = uart1Rx;
uart1Size=0;
}
}
/* USER CODE END 1 */
STM32收到的資料,是這樣處理的,
HAL_UART_Transmit(huart, &RxLenHi, 1, 1000); // 發送長度高位
HAL_UART_Transmit(huart, &RxLenlo, 1, 1000); // 發送長度低位
HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000); // 發送接收到的資料
參考博客——STM32 Uart 接收不定長資料
四、定時器編碼器模式讀取脈沖資料
1、工程配置
- 1、設定
在左側 Pinout&Configuration 界面中的 Timers 下拉中點擊 TIM4,然后在 TIM4 Mode and Configuration 的 Mode 中將 Combined Channels 選擇為 Encoder Mode,即編碼器模式,

在 Configuration 中選擇 Parameter Setting 選項卡,,Counter Mode 默認為 Up,即向上計數,Counter Period 設定為 65535,即計數器周期,這是一個 16 位的自動加載暫存器,填寫范圍為 0~65535,Encoder Mode 設定為 Encoder Mode TI1 and TI2,即兩個輸入 TI1 和 TI2 都被用來作為增量編碼器的介面,Polarity 默認為 Rising Edge,即為捕獲上升沿,其他引數默認,

注意: 當 Encoder Mode 設定為 Encoder Mode TI1 and TI2 模式時,AB 兩相的上升沿和下降沿都會計數,所以計數值是實際脈沖值的 4 倍,即四倍頻,Channel1 和 Channel2 的 Polarity 引數默認是 Rising Edge,意思是在檢測到上升沿的時候就觸發編碼器模式介面捕獲 AB 相的值,并不是指只檢測 AB 相的上升沿,下降沿還是同樣會計數的,
- 2、創建工程
點擊 GENERATE CODE,重新生成代碼,
2、工程代碼
打開 MDK-ARM 工程,按下組合鍵 Ctrl+N(按住 Ctrl 鍵再按 N 鍵),新建一個檔案,再按下組合鍵 Ctrl+S,檔案名改為 encoder.c,保存到 MiaowLabs-DEMO 的 Src 檔案夾里,接著在 MDK-ARM 工程界面左側 Project 欄目雙擊 Application/User 檔案夾,把 encoder.c 加進來,
雙擊 encoder.c 檔案,把下面代碼敲進去
#include "tim.h"//包含tim頭檔案
#include "encoder.h"
int iTim4Encoder;//存放從TIM4定時器讀出來的編碼器脈沖
int GetTim4Encoder(void)//獲取TIM4定時器讀出來的編碼器脈沖
{
iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4));//先讀取脈沖數
__HAL_TIM_SET_COUNTER(&htim4,0);//再計數器清零
return iTim4Encoder;//回傳脈沖數
}

再新建一個檔案 encoder.h 頭檔案,把檔案保存到 Inc 檔案夾,然后,把下面代碼敲進去,
#ifndef __ENCODER_H
#define __ENCODER_H
int GetTim4Encoder(void);//宣告函式
#endif
打開 main.c 檔案,在 main 函式中的 /* USER CODE BEGIN 1 / 和 / USER CODE END 1 */ 之間定義一個變數:
int iTempTim4Encoder; //臨時存放從TIM4編碼器介面捕獲到的脈沖資料

在主回圈 while(1) 里,把以下代碼敲進去:
HAL_Delay(5000);//延時5秒
iTempTim4Encoder = GetTim4Encoder();//捕獲TIM4脈沖資料
printf("TIM4定時器編碼器模式捕獲脈沖 = %d \n",iTempTim4Encoder);//把脈沖資料列印出來
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//翻轉指示燈LED的電平
上面這段加入主回圈的代碼的意思,是 TIM4 定時器的編碼器捕獲脈沖,每隔 5 秒累計輸出一次脈沖資料,通過串口顯示在上位機上,
這里補充一個知識點:M 法測速,就是數固定時間內產生的脈沖數,像這里每隔 5 秒數一次累計起來的脈沖資料,就是用了 M 法測速,
代碼還沒寫完,有一句重要的代碼必須要加進去,TIM4 的編碼器介面模式才會啟用,我們在 main 函式的 /* USER CODE BEGIN 2 / 和 / USER CODE END 2 */ 之間加入:
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);//開啟TIM4的編碼器介面模式
五、PWM與TB6612FNG驅動電機
STM32 與 TB6612FNG 的主要接線:
PB0 --> AIN1
PB1 --> AIN2
PA3 --> BIN1
PA4 --> BIN2
TIM3_CH1 --> PWMA
TIM3_CH2 --> PWMB
1、工程配置
- 1、設定
打開工程,在左側 Pinout&Configuration 界面中的 Timers 下拉中點擊 TIM3,然后在 TIM3 Mode and Configuration 的 Mode 中將 Channel2 選擇為 PWM Generation CH2,并在下方的引數設定選項卡中將 Prescaler 設為 72,即預分頻系數(TIMx_PSC)設為 72;Counter Period 設為 100,即計數周期(自動加載值 TIMx_ARR)設為 100;Pulse 設為 100,即占空比設定為 100%,
回到 STM32CubeMX 軟體界面,在右側界面的芯片中分別點擊 PA3、PA4,并將其配置為 GPIO_Output,在 System Core 下拉選單中選擇 GPIO,然后在左側的 System Core 下拉選單中選擇 GPIO,然后在 GPIO Mode and Configuration 中對 PA3、PA4 引腳進行配置,GPIO output level 代表 GPIO 默認輸出電平,在這里設定為低電平;GPIO mode 代表 GPIO 引腳模式,在這里設定為推挽輸出;GPIO Pull-up/Pull-down 即 GPIO 上拉或下拉,在這里設定為既不上拉也不下拉;Maximum output speed 即 最大輸出速度,在這里設定為低速;User Label 即用戶標簽,在這里將 PA3 改為 BIN1,PA4 改為 BIN2,


- 2、創建工程
點擊 GENERATE CODE,重新生成代碼,
2、工程代碼
在 main.c 中的 /* USER CODE BEGIN 2 / 和 / USER CODE END 2 */ 之間加入以下代碼:
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);//開啟TIM3_CH2的PWM輸出
HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_SET);//初始化BIN1引腳為低電平
HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_RESET);//初始化BIN2引腳為高電平
/* USER CODE END 2 */
然后,在主回圈中加入以下代碼:
ButtonScan();
if(g_iButtonState == 1)//如果按鍵被按下
{
HAL_GPIO_TogglePin(BIN1_GPIO_Port,BIN1_Pin);//翻轉BIN1引腳電平,如果是低電平則翻轉為高電平,如果是高電平則翻轉為低電平
HAL_GPIO_TogglePin(BIN2_GPIO_Port,BIN2_Pin);//翻轉BIN2引腳電平,如果是低電平則翻轉為高電平,如果是高電平則翻轉為低電平
g_iButtonState = 0;//按鍵狀態歸0,代表松開
}
上面這段代碼的意思是,每次按下按鍵,左側電機的轉動方向都會更換一次,默認是全速轉動(占空比 100%),
遇到的問題
1、以CubeMX生成的代碼為基礎,添加用戶代碼
使用時發現每一次重新生成代碼的時候,自己添加進去的代碼就會消失不見,網上查資料發現所有用戶自定義添加的代碼必須在BEGIN和END之間, 還有工程配置也要修改,若是下面的沒有配置,即使把代碼寫在了USER CODE BEGINE 中也會被覆寫,

2、編碼器資料為什么會輸出負數
如果 TI1 和 TI2 分別接電機的 A 相和 B 相的話,那么,當電機正轉的時候,如下圖計數器會向上計數,反轉的時候會向下計數,但是向下計數并不會出現負的值,依舊是從(0-ARR)計數,

獲取編碼器脈沖的代碼如下
int GetTim4Encoder(void)//獲取TIM4定時器讀出來的編碼器脈沖
{
iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4));//先讀取脈沖數
__HAL_TIM_SET_COUNTER(&htim4,0);//再計數器清零
return iTim4Encoder;//回傳脈沖數
}
注意看,上面的代碼 iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4)); 使用了強制型別裝換,把暫存器的值讀出來了之后,轉換成了 short 型(2 位元組),范圍為(-32768-32767),此時當我們把計數器的初始值設定為 0 之后,如果出現反轉,它就會從 0 開始向下計數(0,65535,65534,…)但是經過強制型別轉換之后就變成了(0,-1,-2,…),
但是為什么 65535 會變成 -1 ?此時我們回到 short 的表示范圍(-32768-32767),也就是說原來 int 型變數當讀出來的值為 32767, 32768, 32769,…,65535,65536,65537… 的時候會因為強制轉換成 short 型變數而溢位轉換為 32767,-32768,-32767,…,-1,0,1 就這樣不斷地回圈下去,所以電機反轉的時候讀出的數就是反方向的速度值,不需要用 65535 去減去讀出的值再加上負號才可以得到方便觀察的值,我們只需要運用一個強制型別轉換就可以了,
3、為什么按鍵按下無法控制電機反轉
記得在主回圈里添加 ButtonScan(); 函式
總結
好多好多……沖沖沖!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/229290.html
標籤:其他
