STM32G0系列 IO模拟UART通信

stm32g0 GPIO模拟串口UART通讯 代码下载https://download.csdn.net/download/weixin_42401119/72340612​​​​​​

前面博客有模拟串口的的代码,是采用延时等待的方法,CPU存在空转情况,浪费CPU资源,并且期间不能有太多中断处理,以免影响模拟串口的时序。

本文用stm32G0的GPIO模拟串口通信,使用定时器和外部中断,不用延时等待,不浪费CPU资源。

使用资源:一个定时器,一个普通IO口(TX),一个带外部中断的IO口(RX)。

实现原理:

1、发送部分,通过定时器控制波特率,在定时器中断函数中改变IO口电平来发送数据,定时器中断优先级设置最高,可以保证时序的精确度。

2、接收部分,通过外部下降沿中断,识别起始位,定时器控制波特率,定时器中断优先级设置最高,在定时器中断函数读取电平,也是在电平中间位置读取,大大降低误码率。

3、关于串口程序都在定时器中断函数处理,不用延时等待,发送和接收完成后关闭定时器中断,完全不浪费时间,大大提高CPU使用效率。

此代码暂时没用在量产产品上,不过四个小时100ms的收发测试,还没发现问题,也请广大网友帮忙验证,一起找bug。

注意问题:

1、经过测试,发送波特率最高为256000,接收波特率最高为19200,建议波特率不高于19200。

2、数据格式为:1起始位+8数据位+1停止位,半双工通信,不能同时收发。

3、程序只能接收定长的数据,如果需要接收不定长数据,请网友自行修改(接收倒计时中,超时则认为一帧数据接收完成,再统计数据长度和校验)。

4、定时器中断优先级设置为最高,可以保证时序准确。

5、我的数据包帧头是0xAA,帧尾0x0D,校验值是除了帧尾所有数据的累加和,帧头,帧尾和校验根据用户需求修改。

6、程序用的是LL库。

以下是主要代码:

gpio.c

//TXD为发送端口,配置为普通推挽输出。

//RXD为接收端口,配置为外部下降沿中断。

void MX_GPIO_Init(void)
{

  LL_EXTI_InitTypeDef EXTI_InitStruct = {0};
  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);

  /**/
  LL_GPIO_SetOutputPin(TXD_GPIO_Port, TXD_Pin);

  /**/
  LL_EXTI_SetEXTISource(LL_EXTI_CONFIG_PORTB, LL_EXTI_CONFIG_LINE7);

  /**/
  EXTI_InitStruct.Line_0_31 = LL_EXTI_LINE_7;
  EXTI_InitStruct.LineCommand = ENABLE;
  EXTI_InitStruct.Mode = LL_EXTI_MODE_IT;
  EXTI_InitStruct.Trigger = LL_EXTI_TRIGGER_FALLING;
  LL_EXTI_Init(&EXTI_InitStruct);

  /**/
  LL_GPIO_SetPinPull(RXD_GPIO_Port, RXD_Pin, LL_GPIO_PULL_UP);

  /**/
  LL_GPIO_SetPinMode(RXD_GPIO_Port, RXD_Pin, LL_GPIO_MODE_INPUT);

  /**/
  GPIO_InitStruct.Pin = TXD_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(TXD_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  NVIC_SetPriority(EXTI4_15_IRQn, 0);
  NVIC_EnableIRQ(EXTI4_15_IRQn);

}

tim.c

//系统时钟是64MHz,不分频,即每(1/64)us加1,向上计数,从0加到6667用时104us,对应波特率9600。

void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  LL_TIM_InitTypeDef TIM_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);

  /* TIM3 interrupt Init */
  NVIC_SetPriority(TIM3_IRQn, 0);
  NVIC_EnableIRQ(TIM3_IRQn);

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  TIM_InitStruct.Prescaler = 0;
  TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
  TIM_InitStruct.Autoreload = 6667;                    //波特率9600
  TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
  LL_TIM_Init(TIM3, &TIM_InitStruct);
//  LL_TIM_EnableARRPreload(TIM3);                    //注意不能同步装载
  LL_TIM_SetClockSource(TIM3, LL_TIM_CLOCKSOURCE_INTERNAL);
  LL_TIM_SetTriggerOutput(TIM3, LL_TIM_TRGO_RESET);
  LL_TIM_DisableMasterSlaveMode(TIM3);
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */

}

void USER_TIM3_CONFIG(void)//开启定时器中断
{
	LL_TIM_EnableIT_UPDATE(TIM3);
	LL_TIM_EnableCounter(TIM3);
}

uart_io.c

/**************************发送代码**************************/
void UART_IO_Tx_Start(uint32_t baud)//每次调用该函数就会启动发送程序,发送完成自动结束。
{
	uint32_t autoreload=0;
	autoreload=d_systemclock/baud;//根据波特率计算装载值
	LL_TIM_SetAutoReload(TIM3,autoreload);
	LL_TIM_EnableIT_UPDATE(TIM3);//使能定时器中断
	LL_TIM_EnableCounter(TIM3);  //使能计数
	Tx_status=0;                 //发送时序清零,在定时器中开始发送。
}

uint8_t TxBuff[TXD_size]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
uint8_t Tx_status;//用于控制发送时序
void UART_IO_TxProcess(uint8_t *buff,uint8_t size)
{
	static uint8_t byte_cnt;    //已发送数据个数计数
	static uint8_t bit_cnt;     //已发送位数计数
	static uint8_t Txdata,byte; //数组中要发送的数据,数据中间变量
	switch(Tx_status)
	{
		case 0x00:byte_cnt=0;//准备发送,计数值清零,发送前TXD为高电平
                  TXD_H();
				  Tx_status++;
				  break;
		case 0x01:TXD_L();    //发送起始位
        		  bit_cnt=0;  
				  Txdata=buff[byte_cnt];
				  Tx_status++;
				  break;
		case 0x02:if(bit_cnt==8)//完成一个byte的发送
				  {
					bit_cnt=0;
					TXD_H();
				    byte_cnt++;
					if(byte_cnt==size)//完成一帧数据的发送
					{
						Tx_status=0x03;
						LL_TIM_DisableIT_UPDATE(TIM3);//发送完成,关闭定时器中断,不浪费资源
						LL_TIM_DisableCounter(TIM3);
						LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_7);//开启接收中断
					}
					else
						Tx_status=0x01;
					}
					else    //逐位发送数据
					{	
						byte=Txdata>>bit_cnt;
						if(byte&0x01)
							TXD_H();
						else
							TXD_L();
						bit_cnt++;
					}
					break;
		case 0x03:TXD_H();break;//数据发送完成,TXD拉高。
		default:TXD_H();break;
	}
}

/**************************接收代码**************************/
void UART_IO_Rx_Start(uint32_t baud)//在外部中断中调用该函数就会启动接收程序。
{
	uint32_t autoreload=0;
	autoreload=(d_systemclock/baud/2)*3;
    //识别到外部中断,直接跳过起始位,在第一个bit中间位置读数据,所以是3/2的时间
	LL_TIM_SetCounter(TIM3,0);//清零之前的计数值,保证时序准确
	LL_TIM_SetAutoReload(TIM3,autoreload);
	LL_TIM_EnableIT_UPDATE(TIM3);    //打开定时器中断
	LL_TIM_EnableCounter(TIM3);      //开始计数
	B_RXD_Falling=1;                 //有下降沿的标志
}

uint8_t	B_RXD_Falling;
uint8_t R_RXD_countdown;//接收过程倒计时,放在系统定时器systick中,每1ms减一,直到0
uint8_t B_RXD_decode;   //数据解码标志位,数据格式和校验正确,置1
#define Set_Rx_Countdown()		R_RXD_countdown=10//10ms内没接收到字节,认为本次接收结束。
#define Reset_Rx_Countdown()	R_RXD_countdown=0
uint8_t RxBuff[RXD_size]={0};
void UART_IO_RxProcess(uint8_t *buff,uint8_t size,uint32_t baud)
{
	static uint8_t Rxdata;
	static uint8_t byte_cnt,bit_cnt;
	static uint8_t checksum;
	static uint8_t Rx_status;
	uint32_t autoreload=0;
	if(B_RXD_Falling)//有下降沿
	{
		if(!R_RXD_countdown)//倒计时结束了,接收数据是非连续,清零计数值,开始接收新一帧数据。
		{
			bit_cnt=0;
			byte_cnt=0;
			Rx_status=0;
			checksum=0;
		}
		Set_Rx_Countdown();//如果数据是连续接收的,则倒计时不会结束
		if(!Rx_status)//接收新字节前,清零计数值
		{
			LL_TIM_SetCounter(TIM3,0);
			autoreload=d_systemclock/baud;//之前是3/2的波特率,跳过起始位后,恢复正常的波特率
			LL_TIM_SetAutoReload(TIM3,autoreload);
			Rxdata=0;
			bit_cnt=0;
			Rx_status++;
		}
		if(Rx_status)
		{
			Rxdata>>=1;//接收位
			if(LL_GPIO_IsInputPinSet(RXD_GPIO_Port,RXD_Pin)==SET)
				Rxdata|=0x80;
			if(bit_cnt<7)
				bit_cnt++;
			else
			{
				buff[byte_cnt]=Rxdata;//接收到一个字节后放入数组中
				if(byte_cnt<(size-2))
					checksum+=buff[byte_cnt];//计算校验和
				byte_cnt++;
				if(byte_cnt==size)//接收完一帧数据
				{
					if((buff[0]==d_head)&&(buff[size-1]==d_tail)&&(buff[size-2]==checksum))
						B_RXD_decode=1;//数据格式,校验和正确
				}
				Rx_status=0;
				B_RXD_Falling=0;
				LL_TIM_DisableIT_UPDATE(TIM3);
				LL_TIM_ClearFlag_UPDATE(TIM3);
				LL_TIM_DisableARRPreload(TIM3);
				LL_TIM_DisableCounter(TIM3);
				LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_7);
                //完成一个字节的接收,重新开始起始位中断
			}
		}
	}
}

stm32g0xx_it.c

void SysTick_Handler(void)//1ms
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
	static uint16_t TIM_1s;
	TIM_1s++;
	if(R_RXD_countdown)
		R_RXD_countdown--;
	if(TIM_1s>999)
	{
		TIM_1s=0;
		B_TIM1_JC=1;
	}
	
  /* USER CODE END SysTick_IRQn 0 */

  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

void EXTI4_15_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI4_15_IRQn 0 */

  /* USER CODE END EXTI4_15_IRQn 0 */
  if (LL_EXTI_IsActiveFallingFlag_0_31(LL_EXTI_LINE_7) != RESET)
  {
    LL_EXTI_ClearFallingFlag_0_31(LL_EXTI_LINE_7);
    /* USER CODE BEGIN LL_EXTI_LINE_7_FALLING */
		if(LL_GPIO_IsInputPinSet(RXD_GPIO_Port,RXD_Pin)==RESET)
		{
			LL_EXTI_DisableFallingTrig_0_31(LL_EXTI_LINE_7);
			UART_IO_Rx_Start(d_baud);
		}
    /* USER CODE END LL_EXTI_LINE_7_FALLING */
  }
  /* USER CODE BEGIN EXTI4_15_IRQn 1 */

  /* USER CODE END EXTI4_15_IRQn 1 */
}


void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
	if (LL_TIM_IsActiveFlag_UPDATE(TIM3)==SET)  
	{  
		LL_TIM_ClearFlag_UPDATE(TIM3);
		UART_IO_RxProcess(RxBuff,RXD_size,d_baud);
		UART_IO_TxProcess(TxBuff,TXD_size);
	}	
  /* USER CODE END TIM3_IRQn 0 */
  /* USER CODE BEGIN TIM3_IRQn 1 */

  /* USER CODE END TIM3_IRQn 1 */
}

user_define.h

#define d_head				0xAA    //帧头
#define d_tail				0x0D    //帧尾
#define RXD_size			14      //接收字节数
#define TXD_size			16      //发送字节数
#define d_baud				9600    //波特率
#define d_systemclock	    64000000//系统时钟

#define TXD_H()	LL_GPIO_SetOutputPin(TXD_GPIO_Port, TXD_Pin)
#define TXD_L()	LL_GPIO_ResetOutputPin(TXD_GPIO_Port, TXD_Pin)

main.c

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

  /* USER CODE END 1 */

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

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);

  /* System interrupt init*/
  /* SysTick_IRQn interrupt configuration */
  NVIC_SetPriority(SysTick_IRQn, 3);

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

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

  /* USER CODE BEGIN SysInit */
	SysTick_Config(SystemCoreClock/1000);
	NVIC_SetPriority(SysTick_IRQn,3);
	NVIC_EnableIRQ(SysTick_IRQn);
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
    USER_TIM1_CONFIG();
    USER_TIM3_CONFIG();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		if(B_TIM1_JC)
		{
			B_TIM1_JC=0;
		}
		if(B_RXD_decode)//接收到正确的数据并处理
		{
			B_RXD_decode=0;
			UART_IO_Tx_Start(d_baud);//接收到数据并发送
		}

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

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