欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STM32F103ZE单片机只用一个定时器溢出中断就实现GPIO模拟串口收发数据,不用外部中断和定时器输入捕获中断

程序员文章站 2022-03-06 08:01:56
...

本文使用的单片机为STM32F103ZE,使用的单片机引脚为PA9(发送)和PA10(接收)。晶振为8MHz,程序采用HAL库编写。

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

程序功能:一开始先通过串口输出两行字符,然后开始接收串口字符。每收到10个字符,就打印一次收到的内容,然后继续接收。程序能在有外部晶振或无外部晶振条件下运行,模拟出的波特率为9600或38400的串口经测试没有问题(但是115200波特率不行,速度太快反应不过来导致乱码)。由于只用到了一个定时器溢出中断,所以理论上可以模拟出无数个串口。

程序收到无法显示的控制字符,会以十六进制格式显示出来。

STM32F103ZE单片机只用一个定时器溢出中断就实现GPIO模拟串口收发数据,不用外部中断和定时器输入捕获中断

STM32F103ZE单片机只用一个定时器溢出中断就实现GPIO模拟串口收发数据,不用外部中断和定时器输入捕获中断

STM32F103ZE单片机只用一个定时器溢出中断就实现GPIO模拟串口收发数据,不用外部中断和定时器输入捕获中断

程序代码:

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

 

相关标签: STM32 USART