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改大。
STM32F103ZE单片机只用一个定时器溢出中断就实现GPIO模拟串口收发数据,不用外部中断和定时器输入捕获中断
文章图片

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

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

程序代码:

#include #include #include #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); } #endifstatic 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 = https://www.it610.com/article/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 = https://www.it610.com/article/(data>> 1) | (bit << 7); #if DEBUG if (debug_pos != DEBUG_SIZE) debug_buffer[debug_pos][i] = bits; #endifbits = 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; #endifHAL_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); }


【STM32F103ZE单片机只用一个定时器溢出中断就实现GPIO模拟串口收发数据,不用外部中断和定时器输入捕获中断】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); }


    推荐阅读