STM32 虚拟串口HAL库 开发

STM32HAL库,处理虚拟串口数据(全双工处理 )

今天给大家讲的HAl下面虚拟串口数据开发,解决量产板子串口硬件问题,解决串口不不够用的情况。
废话不多说,先说说串口UART,相信各位在开发中应该用的非常多。功能配置这些我都不做详细介绍了。里面的一个配置需要注意下那就是波特率。虚拟串口需要用软件模拟出这个调度时间。

本篇文章参照借鉴该连接有兴趣的朋友可以去看看:
------------------- 点击 跳转连接 -------------------------
下见串口示例图:
STM32 虚拟串口HAL库 开发_第1张图片

下面是每一位数据在波特不同的情况数据电平持续时间:



当波特率为9600时: 发送1bit数据需要:1/9600 = 104us
当波特率为19200时: 发送1bit数据需要:1/19200= 58us
当波特率为115200时: 发送1bit数据需要: 1/19200= 8.68us

这几个波特率用的多我就列出来了


敲黑板了:波特率高了,对应的电平持续时间就短了,大家根据自己芯片的主频时钟来合理配置对应波特率,这样能有效减少开发时间。

虚拟串口主要用到了 2个GPIO口(一个需要配置成为外部中断),两个定时器用来发送数据时卡时间和接受时卡时间。 为了保证收发时间相较准确我这里用的定时实现

STM32 虚拟串口HAL库 开发_第2张图片

我这里用的随便用的两个GPIO口配置,芯片用的STM32H743,大家随便用STM32的就行。

STM32 虚拟串口HAL库 开发_第3张图片

时钟我这里配置的100MHz,配置1us的定时这里就 100M/100 算下来就1us

这是定时器1的配置,用来做接受数据卡时间用的。

STM32 虚拟串口HAL库 开发_第4张图片

配置一下中断

STM32 虚拟串口HAL库 开发_第5张图片

下面是定时器2 用来发送数据卡时间用的

STM32 虚拟串口HAL库 开发_第6张图片

定时器2 就不需要配置中断了,他只用来记录时间用

定时器3 我用来 跑个延时定时发送串口数据测试用。各位看需求

STM32 虚拟串口HAL库 开发_第7张图片
STM32 虚拟串口HAL库 开发_第8张图片


以上是对应定时器配置,下面是GPIO口配置 输出TX 默认高,上拉,速率高

STM32 虚拟串口HAL库 开发_第9张图片

输入RX 默认上拉,下降沿触发 ,串口输入默认发送报头会有下降沿

STM32 虚拟串口HAL库 开发_第10张图片

到这里基本上硬件配置就配置完成了,下面我们看代码

#define IO_USART_SENDDELAY_TIME  104    //9600 波特率计算值

//枚举类型标记当前位
enum{
 COM_START_BIT,
 COM_D0_BIT,
 COM_D1_BIT,
 COM_D2_BIT,
 COM_D3_BIT,
 COM_D4_BIT,
 COM_D5_BIT,
 COM_D6_BIT,
 COM_D7_BIT,
 COM_STOP_BIT,
};

//GPIO TX脚宏定义
#define iouart1_TXD(n)  if(n) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); \
							  else  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
//GPIO RX引脚宏定义
#define iouart1_RXD  HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)

我在Main 里面周期用虚拟串口打印数据

uint8_t recvData = 0;  //接收数据
uint8_t recvStat = COM_STOP_BIT;  //接收状态

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MPU Configuration--------------------------------------------------------*/
  MPU_Config();

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim3);             //打开循环Time 用于循环发送串口数据
  HAL_TIM_Base_Start(&htim2);				 //打开Time2 用于计数 卡发送时间
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    while (1)
    {
        if(system_flag.bits.system_1ms == 1)  //system loop 1ms
        {
            system_flag.bits.system_1ms = 0;
        }

        if(system_flag.bits.system_10ms == 1)  //system loop 10ms
        {
            system_flag.bits.system_10ms = 0;
        }

        if(system_flag.bits.system_100ms == 1)  //system loop 10ms
        {
            system_flag.bits.system_100ms = 0;

        }
		
		//以上的判定可以忽略  
        if(system_flag.bits.system_500ms == 1)  //周期循环500ms 
        {
            system_flag.bits.system_500ms = 0;
			printf("12111111\r\n");             //串口打印数据
        }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }

  /* USER CODE END 3 */
}

下面是处理的Time2 做的一个延时器。

 /***************************************
* 函数名: Delay_Ms
* 功能说明: 延时
* 形参:nTime,单位为uS
* 返回值: 无
****************************************/
void iouart1_delayUs(volatile uint32_t nTime)
{ 
uint16_t tmp;
 tmp = __HAL_TIM_GetCounter(&htim2);  						//获得 TIM2 计数器的值
 if(tmp + nTime <= 65535)								
  while( (__HAL_TIM_GetCounter(&htim2)-tmp) < nTime );    
 else
 {
  __HAL_TIM_SET_COUNTER(&htim2, 0);//设置 TIM3 计数器寄存器值为0
  while( __HAL_TIM_GetCounter(&htim2) < nTime );
 }
}

有了TIme2的延时函数就可以 写发送函数

#define IO_USART_SENDDELAY_TIME 104 这个值就是上面计算出来的宏定义

/*****************************************
* 函数名: iouart1_SendByte
* 功能说明: 模拟串口发送1字节数据
* 形参:datatoSend
* 返回值: 无
******************************************/
void iouart1_SendByte(uint8_t datatoSend)
{
 uint32_t i, tmp;
 // 开始位
 iouart1_TXD(0); //将TXD的引脚的电平置低
 iouart1_delayUs(IO_USART_SENDDELAY_TIME); 
 for(i = 0; i < 8; i++)
 {
  tmp = (datatoSend >> i) & 0x01;
  if(tmp == 0)
  {
   iouart1_TXD(0);
   iouart1_delayUs(IO_USART_SENDDELAY_TIME); //0  
  }
  else
  {
   iouart1_TXD(1);
   iouart1_delayUs(IO_USART_SENDDELAY_TIME); //1  
  }
 }
 // 结束位
 iouart1_TXD(1);//将TXD的引脚的电平置?
 iouart1_delayUs(IO_USART_SENDDELAY_TIME); 
}

/*****************************************
* 函数名: iouart1_SendByte
* 功能说明: 模拟串口发送多字节数据
* 形参:SendData
* 返回值: 无
******************************************/
void  iouart1_sendBuff(const char *SendData)
{
	for(int i=0;i<strlen(SendData);i++)
	{
		iouart1_SendByte(SendData[i]);
	}
}

上面是处理发送部比较简单,下面是接受部分的代码

上面截图里面提到Time的 计数周期要做修改 所以

STM32 虚拟串口HAL库 开发_第11张图片

/*****************************************
* 函数名: MX_TIM1_Init
* 功能说明: Tim1初始化
* 形参:无
* 返回值: 无
******************************************/
void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 99;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = IO_USART_SENDDELAY_TIME;    //这里是上面宏定义时间,每次中断到达了就进行数据数据
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */

}

GPIO和接受数据中断处理

/*****************************************
* 函数名: MX_GPIO_Init
* 功能说明: GPIO初始化
* 形参:无
* 返回值: 无
******************************************/
void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);

  /*Configure GPIO pin : PB6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pin : PB7 */
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}

/* USER CODE BEGIN 2 */

//************************************************************************
 这里以上的都是软件生成不用去管他,下面是串口接受数据时外部中断触发 就起始位进入时处理的
//*************************************************************************
/*****************************************
* 函数名: HAL_GPIO_EXTI_Callback
* 功能说明: GPIO中断回调函数
* 形参:GPIO_Pin
* 返回值: 无
******************************************/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin==GPIO_PIN_7)
	{
		if(iouart1_RXD == 0) 
		{
		 if(recvStat == COM_STOP_BIT)
		 {
				recvStat = COM_START_BIT;
				//这要延时下 跳过起始位等待
				iouart1_delayUs(50);
				//开启Time1中断计数
				HAL_TIM_Base_Start_IT(&htim1);
		 }
	}
}

下面是串口接受的主要 函数每次中断计数溢出触发就去读取数据

/*****************************************
* 函数名: HAL_TIM_PeriodElapsedCallback
* 功能说明: TIME中断回调函数
* 形参:htim
* 返回值: 无
******************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	static uint8_t system10ms_cnt =0;
	static uint8_t system100ms_cnt =0;
	static uint16_t system500ms_cnt =0;
	if(htim->Instance == TIM3)     //如果是TIM3 触发中断
	{
		system_flag.bits.system_1ms = 1;
		if((++system10ms_cnt) >= 10)
		{
			system10ms_cnt =0;
			system_flag.bits.system_10ms = 1;
		}
		
		if((++system100ms_cnt) >= 100)
		{
			system100ms_cnt =0;
			system_flag.bits.system_100ms = 1;
		}		
		
		if((++system500ms_cnt) >= 500)
		{
			system500ms_cnt =0;
			system_flag.bits.system_500ms = 1;
		}
	}
	
else if(htim->Instance == TIM1)    //如果是TIM1 触发中断
{
  recvStat++;
  if(recvStat == COM_STOP_BIT)
  {
    HAL_TIM_Base_Stop_IT(&htim1);    
    //到这就接收到完整的1个字节数据
   recvData = recvData;    
   return;
  }
  if(iouart1_RXD)
  {
   recvData |= (1 << (recvStat - 1));
  }else{
  recvData &= ~(1 << (recvStat - 1));
	}
		
	}
}

以上的基本的串口数据收发就完成了;

下面还是要讲一下 重定义发送函数 用来打印状态处理, 如果你想用printf 函数打印数据,拿小本本记一下

#include "stdio.h"
#include "string.h"
/*****************************************
* 函数名: fputc
* 功能说明: 重定义函数
* 形参:ch,f
* 返回值: int
******************************************/
int fputc(int ch, FILE *f)
{
	uint16_t count = 0;
	iouart1_sendBuff((unsigned char *)&ch);  //调用打印串函数
    return ch;
}

如果是Keil开发的,这个微库勾上

STM32 虚拟串口HAL库 开发_第12张图片

附一张输出效果

STM32 虚拟串口HAL库 开发_第13张图片

还有,这个代码可以升级升级(有兴趣的可以试试)

  1. 空闲检测: 加一个累计计数的标志位,当接受数据停止的时候( 检测到 COM_STOP_BIT)进行累计计数 ,超过设定阈值就认为进入了空闲状态。

  2. 队列接受存储数据,加入数组或者指针实现也行。

  3. 串口错误帧检测,也是加一个累计计数标志位,从接受数据开始计算,如果没有检测到 COM_STOP_BIT 并且当前计数已经超过阈值,就 判定位错误帧。

最后有疑问的小伙伴 可以加入技术交流群

QQ群号: 764284134

STM32 虚拟串口HAL库 开发_第14张图片

你可能感兴趣的:(STM32,STM32资料,stm32,单片机,arm)