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 */
}