1069|1

20

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

[测评nucleo开发板f413zh]第4篇串口调试 [复制链接]

本帖最后由 机器人爱好者1991 于 2023-10-19 16:40 编辑

    

        单片机的串口在调试中非常重要。串口可以用于打印单片机的变量数值,方便排查问题。在调试中都是非常重要的工具,可以显著的提高调试效率。

一、引脚分配。

        串口用到了stm32重要的外设资源,uart。对于这个板子是没有串口转换芯片的,直接就是TX、RX的引脚。原理图的CN9标注USART的地方,4和6引脚,分别是PD6(RX)和PD5(TX)引脚。我们就选择这个。

二、串口的概念

        串口是一种通信接口,用于在设备之间传输数据。在STM32中,串口主要分为UART和USART两种类型。
        UART是通用异步收发传输器(Universal Asynchronous Receiver Transmitter)的缩写,它是一种简单的串口通信方式。UART只支持异步通信,也就是数据的传输不需要时钟信号,而是通过起始位、数据位、校验位和停止位来进行数据的传输。
        USART是通用同步异步收发传输器(Universal Synchronous/Asynchronous Receiver Transmitter)的缩写,它是一种更为复杂的串口通信方式。USART既支持异步通信,也支持同步通信。异步通信与UART相似,而同步通信则需要使用外部时钟信号来同步数据的传输。
        总的来说,UART和USART都是串口通信的方式,UART是一种简单的异步通信方式,而USART则是一种更为复杂的通信方式,支持异步和同步通信。在选择使用哪种串口通信方式时,需要根据具体的应用需求和外部设备的要求来决定。

三、串口的配置

        配置相对简单,就是设置Mode为异步,打开串口2的全局中断,设置引脚,设置比特率等信息。

四、串口的发送

        代码主要用于在STM32微控制器上配置和管理USART2的UART通信。 MX_USART2_UART_Init 函数用于初始化USART2的通信参数,包括波特率、数据位、停止位等,然后通过调用HAL_UART_Init函数完成初始化。

        HAL_UART_MspInit 函数负责配置与USART2相关的GPIO引脚和中断,包括使能USART2时钟、配置GPIO引脚为复用模式、设置中断优先级等。在STM32微控制器中,一个GPIO引脚可以有多种不同的功能,比如作为普通数字输入/输出、串行通信接口(如USART、SPI、I2C)的数据线,或者其他功能。这种切换功能的能力称为引脚的复用功能。Alternate 字段是一个用来指定GPIO引脚复用功能的设置。在这里,GPIO_AF7_USART2 指定了将这些引脚配置为与USART2通信相关的功能。

        最后,HAL_UART_MspDeInit 函数用于反初始化USART2,包括禁用USART2时钟、释放GPIO引脚配置以及禁用中断。这些函数协同工作以确保USART2 UART通信正确初始化和反初始化。

        中断发送。while(huart2.gState != HAL_UART_STATE_READY);这是一个 while 循环,用于等待USART2的发送状态变为 HAL_UART_STATE_READY,即等待数据发送完成。在这个循环中,程序会不断检查 huart2.gState 的状态,如果不等于 HAL_UART_STATE_READY,就会一直循环等待。一旦 huart2.gState 变为 HAL_UART_STATE_READY,表示数据发送完成,循环结束,程序继续执行后续操作。

HAL_UART_Transmit(&huart2, (uint8_t*)send_buffer, strlen(send_buffer), HAL_MAX_DELAY);
while(huart2.gState != HAL_UART_STATE_READY);

 

五、串口的接收

        阻塞接收。定义一个数组 uint8_t RxBuffer[10] = {0};直接在while(1)里面,设置接收的长度以及超时时间1s。跳出的条件是超时或者是接收完10个字节。

if(HAL_UART_Receive(&huart1,RxBuffer,sizeof(RxBuffer),1000) == HAL_OK)
{
	HAL_UART_Transmit(&huart1,RxBuffer,sizeof(RxBuffer),100);
}

        中断接收。在UI界面开启中断与设置中断函数。设置中断接收HAL_UART_Receive_IT(&huart2, (uint8_t *)&usart2_rx_buffer, 1);和中断处理函数void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)进行组合。

uint8_t RxBuffer[8] = {0,1,2,3,4,5,6,7};
void MX_USART2_UART_Init(void)
{
     /* USER CODE BEGIN USART2_Init 2 */
    HAL_UART_Receive_IT(&huart2, RxBuffer, sizeof(RxBuffer));
    /* USER CODE END USART2_Init 2 */
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart2)
    {
        HAL_UART_Transmit(&huart2, RxBuffer, sizeof(RxBuffer), 100);
        memset(RxBuffer, 0, sizeof(RxBuffer));
        HAL_UART_Receive_IT(&huart2, RxBuffer, sizeof(RxBuffer));
    }
}

 

        我这里出现的问题是大于sizeof(RxBuffer)之后,就不进入中断了。实际想接收8个字符/次,上位机发送9个字符之后,整个串口中断就进去不了了,缓冲区阻塞了。最后,将RxBuffer的size设置成了1,会好一些;又将波特率从115200变成9600就更好了。这个问题以后有时间再详细探究。

        DMA接收。DMA的接收有时间再补,随便写可能会有遗漏。

六、通信协议数据的发送接收

6.1 接收字符的处理

        因为我发送的0x01的时间和0x02的速度,消息不定长,我们采用的是每次接收一个字符。然后进行拼接。不只是单片机,我的上位机也使用了一次一个字符进行拼接的策略。我定义了一个struct,struct里面是rx_buffer来存储接收缓冲;rx_index表示接收第几个字节了;frame_started类似于一个bool数值,表示帧接收完帧头否。

typedef struct
{
    uint8_t rx_buffer[256]; // 接收缓冲
    uint8_t rx_index; // 接收缓冲区索引
    uint8_t frame_started; // 帧接收完帧头否
} rx_data_t;

        下面的接收流程,就是一个个的字符接收,然后拼成完整的一帧。主要功能是根据特定的帧头和帧尾标识,进行 CRC 校验,最后解析接收到的数据帧。

        这里面分为帧头的检测、帧尾的检测、CRC16-Modbus的校验,完整数据的输入解析。如果索引为0且接收到的字符等于帧头1(0x55),则重置接收,将接收索引设置为1,并将接收到的字符存入接收缓冲区。如果索引为1且接收到的字符等于帧头2(0xaa),则设置frame_started标志表示有新的数据帧开始,将接收索引设置为2,并将接收到的字符存入接收缓冲区。如果索引大于等于2且frame_started标志为真,则将接收到的字符存入接收缓冲区,并递增接收索引。判断是否接收到了帧尾。如果接收索引大于等于6且接收缓冲区倒数第二个字符等于帧尾1,并且倒数第一个字符等于帧尾2,则执行以下操作:调用函数计算接收缓冲区中数据部分的 CRC16 校验值,函数解析接收到的数据帧。最后,重置frame_started标志、接收索引和接收缓冲区,以准备接收下一帧数据。

void recv_uchar(const uint8_t *buffer, const int16_t length)
{   
    uint8_t crc_low, crc_high;
    rx_buffer_temp[0]=buffer[0];
    // 1. 如果,索引为0而且收到等于帧头1的字符,就得重置接收
    if (rx_data.rx_index == 0 && rx_buffer_temp[0] == 0x55)
    {
        rx_data.rx_index = 1;
        rx_data.rx_buffer[0] = rx_buffer_temp[0];
    }
    // 2. 如果,索引为1而且收到等于帧头2的字符,设置个 frame_started 来表示有新数据了
    else if (rx_data.rx_index == 1 && rx_buffer_temp[0] == 0xaa)
    {
        rx_data.rx_index = 2;
        rx_data.frame_started = 1;
        rx_data.rx_buffer[1] = rx_buffer_temp[0];
    }
    // 3. 对数组[2]以后的接收处理
    else if (rx_data.rx_index >= 2 && rx_data.frame_started)
    {
        rx_data.rx_buffer[rx_data.rx_index++] = rx_buffer_temp[0];
        // 4. 判断是否收到了帧尾
        if (rx_data.rx_index >= 6 && rx_data.rx_buffer[rx_data.rx_index - 2] == frame_footer1 &&
            rx_data.rx_buffer[rx_data.rx_index - 1] == frame_footer2)
        {
            // 5. crc16校验
            calculate_crc(&rx_data.rx_buffer[5], rx_data.rx_buffer[4], &crc_low, &crc_high);
            if (crc_low == rx_data.rx_buffer[rx_data.rx_index - 4] &&
                crc_high == rx_data.rx_buffer[rx_data.rx_index - 3])
            {
                // printf("CRC success\n");
                parse_serial_data(rx_data.rx_buffer, rx_data.rx_index);
            }
            else
            {
                printf("CRC failed\n");
            }
            rx_data.frame_started = 0; 
            rx_data.rx_index = 0; 
            memset(rx_data.rx_buffer,0,sizeof(rx_data.rx_buffer));
        }
    }
    else
    {
        ;
    }
}

 

 

        对于速度数据的处理,就是按照小端格式就行赋值即可。

 /*解析返回的速度*/
void recv_velocity_cmd(const uint8_t *buffer, const int16_t length, int16_t recv_velocity[2])
{
	if (0x06 != buffer[2] || 0x02 != buffer[3])
	{
	 return;
	}
	recv_velocity[0] = (int16_t *)(buffer[5] + ((int16_t)(buffer[6]) << 8));
	recv_velocity[1] = (int16_t *)(buffer[7] + ((int16_t)(buffer[8]) << 8));
	printf("left_velocity=%d,right_velocity=%d\n", recv_velocity[0], recv_velocity[1]);
    send_velocity[0]=recv_velocity[0];
    send_velocity[1]=recv_velocity[1];
}
/*解析返回的时间*/
void recv_time_cmd(const uint8_t *buffer, const int16_t length, int16_t recv_beijing_time[6])
{
	if (0x06 != buffer[2] || 0x01 != buffer[3])
	{
	 return;
	}
	recv_beijing_time[0] = (int16_t *)(buffer[5] + ((int16_t)(buffer[6]) << 8));
	recv_beijing_time[1] = (int16_t *)(buffer[7] + ((int16_t)(buffer[8]) << 8));
	recv_beijing_time[2] = (int16_t *)(buffer[9] + ((int16_t)(buffer[10]) << 8));
	recv_beijing_time[3] = (int16_t *)(buffer[11] + ((int16_t)(buffer[12]) << 8));
	recv_beijing_time[4] = (int16_t *)(buffer[13] + ((int16_t)(buffer[14]) << 8));
	recv_beijing_time[5] = (int16_t *)(buffer[15] + ((int16_t)(buffer[16]) << 8));
	printf("year=%d,month=%d,day=%d,hour=%d,minute=%d,second=%d\n", recv_beijing_time[0],recv_beijing_time[1],recv_beijing_time[2],recv_beijing_time[3],recv_beijing_time[4],recv_beijing_time[5]);
}

6.2 字符命令的发送

        按照0-1为帧头,0x06表示写入,0x01表示时间,下一个字节是数据的长度,下面的按照小端格式对年月日时分秒,进行每个变量2个字节的占位进行赋值。接线来进行CRC16的校验填充,最后加两个帧尾。

void send_current_time(const int16_t beijing_time[6])
{
	uint8_t write_data[21];
	uint8_t crc_low, crc_high;
	int16_t send_index = 0;

	write_data[send_index++] = frame_header1;
	write_data[send_index++] = frame_header2;
	write_data[send_index++] = 0x06; // 写入
	write_data[send_index++] = 0x01; // 时间
	write_data[send_index++] = 12;   // 数据长度

	// 将年、月、日、时、分、秒按小端格式存储到数组中
	write_data[send_index++] = beijing_time[0] & 0xFF;
	write_data[send_index++] = beijing_time[0] >> 8;
	write_data[send_index++] = beijing_time[1] & 0xFF;
	write_data[send_index++] = beijing_time[1] >> 8;
	write_data[send_index++] = beijing_time[2] & 0xFF;
	write_data[send_index++] = beijing_time[2] >> 8;
	write_data[send_index++] = beijing_time[3] & 0xFF;
	write_data[send_index++] = beijing_time[3] >> 8;
	write_data[send_index++] = beijing_time[4] & 0xFF;
	write_data[send_index++] = beijing_time[4] >> 8;
	write_data[send_index++] = beijing_time[5] & 0xFF;
	write_data[send_index++] = beijing_time[5] >> 8;

	// 计算CRC16校验
	calculate_crc(&write_data[5], write_data[4], &crc_low, &crc_high);
	// 将CRC校验值赋值给write_data[16]和write_data[17]
	write_data[send_index++] = crc_low;
	write_data[send_index++] = crc_high;
	write_data[send_index++] = frame_footer1;
	write_data[send_index++] = frame_footer2;
	HAL_UART_Transmit(&huart2,write_data,send_index,100);
}

 

 

 

6.3 串口2的一些配置

        当然得使能一下串口2的printf。

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

PUTCHAR_PROTOTYPE
{
 // 将字符发送到UART2
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
 return ch;
}

        串口2的初始化。

typedef struct
{
    uint8_t rx_buffer[256]; // 接收缓冲
    uint8_t rx_index; // 接收缓冲区索引
    uint8_t frame_started; // 帧接收完帧头否
} rx_data_t;
rx_data_t rx_data; // 接收相关数据的结构体实例
uint8_t rx_buffer_temp[1] = {0};


void MX_USART2_UART_Init(void)
{
    /* USER CODE BEGIN USART2_Init 2 */
    HAL_UART_Receive_IT(&huart2, rx_buffer_temp, sizeof(rx_buffer_temp));
    rx_data.rx_index=0;
    rx_data.frame_started=0;
    memset(rx_data.rx_buffer,0,sizeof(rx_data.rx_buffer));
    /* USER CODE END USART2_Init 2 */
}

        串口2的单个字符接收函数。

// 接收数据回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    uint8_t crc_low, crc_high;
    if (huart == &huart2)
    {
        rx_buffer_temp[0]=huart->Instance->DR;
        // 1. 如果,索引为0而且收到等于帧头1的字符,就得重置接收
        if (rx_data.rx_index == 0 && rx_buffer_temp[0] == 0x55)
        {
            rx_data.rx_index = 1;
            rx_data.rx_buffer[0] = rx_buffer_temp[0];
            // printf("recv header1\n");
        }
        // 2. 如果,索引为1而且收到等于帧头2的字符,设置个 frame_started 来表示有新数据了
        else if (rx_data.rx_index == 1 && rx_buffer_temp[0] == 0xaa)
        {
            rx_data.rx_index = 2;
            rx_data.frame_started = 1;
            rx_data.rx_buffer[1] = rx_buffer_temp[0];
            // printf("recv header2\n");
        }
        // 3. 对数组[2]以后的接收处理
        else if (rx_data.rx_index >= 2 && rx_data.frame_started)
        {
            rx_data.rx_buffer[rx_data.rx_index++] = rx_buffer_temp[0];
            // 4. 判断是否收到了帧尾
            if (rx_data.rx_index >= 6 && rx_data.rx_buffer[rx_data.rx_index - 2] == frame_footer1 &&
                rx_data.rx_buffer[rx_data.rx_index - 1] == frame_footer2)
            {
                // printf("recv footer\n");
                // HAL_UART_Transmit(&huart2, rx_data.rx_buffer, rx_data.rx_index, HAL_MAX_DELAY);
                // 5. crc16校验
                calculate_crc(&rx_data.rx_buffer[5], rx_data.rx_buffer[4], &crc_low, &crc_high);
                // HAL_UART_Transmit(&huart2, &rx_data.rx_buffer[5], rx_data.rx_buffer[4], HAL_MAX_DELAY);
                // HAL_UART_Transmit(&huart2, crc_low, 1, HAL_MAX_DELAY);
                // HAL_UART_Transmit(&huart2, crc_high, 1, HAL_MAX_DELAY);
                if (crc_low == rx_data.rx_buffer[rx_data.rx_index - 4] &&
                    crc_high == rx_data.rx_buffer[rx_data.rx_index - 3])
                {
                    printf("CRC success\n");
                    ;
                 	recv_data_parse(rx_data.rx_buffer, rx_data.rx_index);
                }
                else
                {
                    printf("CRC failed\n");
                    recv_data_parse(rx_data.rx_buffer, rx_data.rx_index);

                    // HAL_UART_Transmit(&huart2, rx_data.rx_buffer, rx_data.rx_index, HAL_MAX_DELAY);
                    // HAL_UART_Transmit(&huart2, &rx_data.rx_buffer[5], rx_data.rx_buffer[4], HAL_MAX_DELAY);
                    // HAL_UART_Transmit(&huart2, crc_low, 1, HAL_MAX_DELAY);
                    // HAL_UART_Transmit(&huart2, crc_high, 1, HAL_MAX_DELAY);
                    // HAL_UART_Transmit(&huart2, rx_data.rx_buffer[rx_data.rx_index - 4], 1, HAL_MAX_DELAY);
                    // HAL_UART_Transmit(&huart2, rx_data.rx_buffer[rx_data.rx_index - 3], 1, HAL_MAX_DELAY);
                }
                rx_data.frame_started = 0; 
                rx_data.rx_index = 0; 
                memset(rx_data.rx_buffer,0,sizeof(rx_data.rx_buffer));

            }
        }
        else
        {
            ;
        }
        memset(rx_buffer_temp, 0, sizeof(rx_buffer_temp));
        HAL_UART_Receive_IT(&huart2, rx_buffer_temp, sizeof(rx_buffer_temp));
    }
}

 

6.3 测试

        在windows上打开串口8,按下按键,会发送相应的指令到下位机。下位机将收到的指令recv数组赋值给send数组,发送到上位机,进行解析。

        windows下的上位机,出现了一些汉字显示的问题,删除汉字,使用下面的指令编译了一下。

C:\mingw64\bin\x86_64-w64-mingw32-gcc -o serial.exe serial_server.c -lpthread

 

此帖出自stm32/stm8论坛
点赞 关注
 

回复
举报

20

帖子

0

TA的资源

一粒金砂(中级)

沙发
 
Core.zip (36.45 KB, 下载次数: 4) scripts.zip (32.47 KB, 下载次数: 0)
此帖出自stm32/stm8论坛
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表