本文使用的單片機爲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);
}