STM32F103ZE單片機只用一個定時器溢出中斷就實現GPIO模擬串口收發數據,不用外部中斷和定時器輸入捕獲中斷

本文使用的單片機爲STM32F103ZE,使用的單片機引腳爲PA9(發送)和PA10(接收)。晶振爲8MHz,程序採用HAL庫編寫。

程序下載地址:https://pan.baidu.com/s/13DEWdpupCG3REGTTAJYuVw

程序功能:一開始先通過串口輸出兩行字符,然後開始接收串口字符。每收到10個字符,就打印一次收到的內容,然後繼續接收。程序能在有外部晶振或無外部晶振條件下運行,模擬出的波特率爲9600或38400的串口經測試沒有問題(但是115200波特率不行,速度太快反應不過來導致亂碼)。由於只用到了一個定時器溢出中斷,所以理論上可以模擬出無數個串口。

程序收到無法顯示的控制字符,會以十六進制格式顯示出來。

需要注意的是,串口字符收發完全依賴於定時器溢出中斷,所以跟printf函數綁定後,必須防止棧溢出。當棧快要溢出的時候,printf函數內部會自動將所有中斷關閉,這樣程序就會卡死在fputc裏面。如果出現了這種情況,可以減小局部變量(特別是數組)的大小,或者把啓動文件(startup_stm32f103xe.s)裏面的Stack_Size改大。

程序代碼:

#include <ctype.h>
#include <stdio.h>
#include <stm32f1xx.h>

#define DEBUG 0
#define USE_HSI 0

#define MYUART_TX_0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET)
#define MYUART_TX_1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET)
#define MYUART_RX (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10) == GPIO_PIN_SET)

TIM_HandleTypeDef htim2;
static uint8_t myuart_txbuf, myuart_txe = 1;
static uint8_t myuart_rxbuf, myuart_rxne;
#if DEBUG
#define DEBUG_SIZE 30
static uint8_t debug_buffer[DEBUG_SIZE][9]; // 記錄採樣結果 (起始位+8位數據位), 以便調試
static int debug_pos;
#endif

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1);
}
#endif

static void clock_init(void)
{
  HAL_StatusTypeDef status;
  RCC_ClkInitTypeDef clk = {0};
  RCC_OscInitTypeDef osc = {0};

  // 啓動外部8MHz晶振, 並倍頻到72MHz
  osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  osc.HSEState = RCC_HSE_ON;
  osc.PLL.PLLMUL = RCC_PLL_MUL9;
  osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  osc.PLL.PLLState = RCC_PLL_ON;
#if USE_HSI
  status = HAL_ERROR;
#else
  status = HAL_RCC_OscConfig(&osc);
#endif
  
  // 如果外部晶振啓動失敗, 則使用內部晶振, 並倍頻到64MHz
  if (status != HAL_OK)
  {
    osc.HSEState = RCC_HSE_OFF;
    osc.PLL.PLLMUL = RCC_PLL_MUL16;
    osc.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
    HAL_RCC_OscConfig(&osc);
  }
  
  // 配置外設總線分頻係數
  __HAL_RCC_ADC_CONFIG(RCC_ADCPCLK2_DIV6);
  clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
  clk.APB1CLKDivider = RCC_HCLK_DIV2;
  clk.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2);
}

static void myuart_init(int baud_rate)
{
  GPIO_InitTypeDef gpio = {0};
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_TIM2_CLK_ENABLE();
  
  MYUART_TX_1;
  gpio.Mode = GPIO_MODE_OUTPUT_PP;
  gpio.Pin = GPIO_PIN_9;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
  
  htim2.Instance = TIM2;
  htim2.Init.Period = SystemCoreClock / baud_rate / 3 - 1;
  HAL_TIM_Base_Init(&htim2);
  
  HAL_NVIC_EnableIRQ(TIM2_IRQn);
  HAL_TIM_Base_Start_IT(&htim2);
}

static int myuart_recv(uint8_t *data)
{
  if (myuart_rxne)
  {
    *data = myuart_rxbuf;
    myuart_rxne = 0;
    return 1;
  }
  return 0;
}

static void myuart_recv_process(void)
{
  static int8_t i, j;
  static uint8_t bits, data;
  uint8_t bit;
  
  bit = MYUART_RX; // 採樣
  // i=0: 起始位; i=1~8: 數據位; i=9: 停止位
  // j=0~2: 每位採樣三次, bit爲當前採樣值
  
  if (i == 0 && j == 0 && bit)
    return; // 未收到起始位
  else if (i == 9)
  {
    // 檢測停止位
    if (bit)
    {
      // 一檢測到停止位, 就立即停止接收, 開始檢測下一個起始位
      i = 0;
#if DEBUG
      if (debug_pos != DEBUG_SIZE)
        debug_pos++;
#endif
      if (!myuart_rxne)
      {
        myuart_rxbuf = data;
        myuart_rxne = 1;
      }
    }
    return;
  }
  
  bits = (bits << 1) | bit; // 將採樣結果依次記錄到bits變量的低三位中
  j++;
  if (j == 3)
  {
    // 三次採樣完畢
    j = 0;
    
    // 根據三次採樣結果, 確定數據的第i位是什麼
    if (bits == 3 || bits >= 5)
      bit = 1; // 如果採到1的次數比0多, 則認爲該位爲1
    else
      bit = 0;
    data = (data >> 1) | (bit << 7);
    
#if DEBUG
    if (debug_pos != DEBUG_SIZE)
      debug_buffer[debug_pos][i] = bits;
#endif
    
    bits = 0;
    i++;
  }
}

static void myuart_send(uint8_t data)
{
  while (!myuart_txe);
  myuart_txbuf = data;
  myuart_txe = 0;
}

static void myuart_send_process(void)
{
  static uint8_t i;
  static uint16_t bits;
  
  if (i == 0)
  {
    if (myuart_txe)
      return;
    
    bits = (myuart_txbuf << 1) | 0x200;
  }
  
  if (i % 3 == 0)
  {
    if (bits & 1)
      MYUART_TX_1;
    else
      MYUART_TX_0;
    bits >>= 1;
  }
  
  i++;
  if (i == 30)
  {
    myuart_txe = 1;
    i = 0;
  }
}

int main(void)
{
  int err, ret;
  int i;
  uint8_t data[10];
#if DEBUG
  int j;
#endif
  
  HAL_Init();
  
  clock_init();
  myuart_init(38400);
  
  printf("STM32F103ZE UART\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  while (1)
  {
    // 接收串口字符, 直到填滿data數組
    for (i = 0; i < sizeof(data); i++)
    {
      do
      {
        ret = myuart_recv(&data[i]);
      } while (!ret);
    }
    
    // 打印出每個字符
    err = 0;
    for (i = 0; i < sizeof(data); i++)
    {
      if (isprint(data[i]))
        printf("%c", data[i]);
      else
      {
        printf("{%#x}", data[i]);
        err++;
      }
    }
    printf("\n");
    
    // 如果收到了非打印字符, 則打印出調試信息
#if DEBUG
    if (err)
    {
      for (i = 0; i < debug_pos; i++)
      {
        for (j = 0; j < 10; j++)
          printf("%d%d%d ", (debug_buffer[i][j] >> 2) & 1, (debug_buffer[i][j] >> 1) & 1, debug_buffer[i][j] & 1);
        printf("\n");
      }
    }
    debug_pos = 0;
#endif
  }
}

void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim2);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim == &htim2)
  {
    // 這裏理論上可以放置很多對串口收發函數
    // 用一個定時器就能模擬出很多個相同波特率的串口
    myuart_recv_process();
    myuart_send_process();
  }
}

void HardFault_Handler(void)
{
  while (1);
}

void SysTick_Handler(void)
{
  HAL_IncTick();
}

/* 串口綁定printf (不需要勾選Use MicroLIB) */
#pragma import(__use_no_semihosting)

struct __FILE
{
  int handle;
} __stdout, __stderr;

int fputc(int ch, FILE *fp)
{
  if (fp == stdout || fp == stderr)
  {
    if (ch == '\n')
      myuart_send('\r');
    myuart_send(ch);
  }
  return ch;
}

void _sys_exit(int returncode)
{
  while (1);
}

 

STM32L051R8單片機的最大頻率爲32MHz,用來模擬38400波特率的串口時,必須在定時器中斷函數中直接判斷定時器溢出標誌位,不能在HAL_TIM_IRQHandler裏面繞一圈,否則時間上來不及。

void TIM2_IRQHandler(void)
{
  if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET)
  {
    __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
    myuart_recv_process();
    myuart_send_process();
  }
}

STM32L051R8單片機使用LPTIM定時器模擬串口的代碼:

(特別注意,STM32L系列單片機I/O口默認爲analog模式,不是input模式,所以必須在代碼中將PA10配置爲輸入,否則PA9最開始發送的兩個字符會亂碼,並且不能接收任何字符)

LPTIM_HandleTypeDef hlptim1;

static void clock_init(void)
{
  HAL_StatusTypeDef status;
  RCC_ClkInitTypeDef clk = {0};
  RCC_OscInitTypeDef osc = {0};
  
  osc.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_HSI;
  osc.HSEState = RCC_HSE_ON;
  osc.HSIState = RCC_HSI_ON;
  osc.PLL.PLLDIV = RCC_PLLDIV_3;
  osc.PLL.PLLMUL = RCC_PLLMUL_8;
  osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  osc.PLL.PLLState = RCC_PLL_ON;
  status = HAL_RCC_OscConfig(&osc);
  if (status != HAL_OK)
    exit(-1);
  
  clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
  clk.APB1CLKDivider = RCC_HCLK_DIV1;
  clk.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_1);
  
  if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET)
  {
    osc.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    osc.LSEState = RCC_LSE_ON;
    HAL_RCC_OscConfig(&osc);
  }
}

static void myuart_init(int baud_rate)
{
  GPIO_InitTypeDef gpio = {0};
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_LPTIM1_CLK_ENABLE();
  
  MYUART_TX_1;
  gpio.Mode = GPIO_MODE_OUTPUT_PP;
  gpio.Pin = GPIO_PIN_9;
  gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
  
  gpio.Mode = GPIO_MODE_INPUT;
  gpio.Pin = GPIO_PIN_10;
  HAL_GPIO_Init(GPIOA, &gpio);
  
  hlptim1.Instance = LPTIM1;
  hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
  hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
  HAL_LPTIM_Init(&hlptim1);
  
  HAL_NVIC_EnableIRQ(LPTIM1_IRQn);
  HAL_LPTIM_Counter_Start_IT(&hlptim1, SystemCoreClock / baud_rate / 3 - 1);
}

void LPTIM1_IRQHandler(void)
{
  if (__HAL_LPTIM_GET_FLAG(&hlptim1, LPTIM_FLAG_ARRM) != RESET)
  {
    __HAL_LPTIM_CLEAR_FLAG(&hlptim1, LPTIM_FLAG_ARRM);
    myuart_recv_process();
    myuart_send_process();
  }
  if (__HAL_LPTIM_GET_FLAG(&hlptim1, LPTIM_FLAG_ARROK) != RESET)
    __HAL_LPTIM_CLEAR_FLAG(&hlptim1, LPTIM_FLAG_ARROK);
}

 

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