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);
}

 

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