分类: 智能创造

  • uart协议实现双stm32单片机通信

    //引言

    学校robomaster校内赛规定主控需使用c8t6,但因笔者想尝试引入编码器电机做简单的pid电机控制,而c8t6只有4个定时器,无法同时实现4路编码器均用定时器计数(电机速度快,采用中断模式有阻塞CPU风险)和pwm输出控制电机舵机等外设。

    故思考许久采用双c8t6主从控模式,主控负责接收遥控器信号、电机方向发送、编码器速度接收、pid计算pwm输出,从控负责电机方向接收、编码器速度识别与发送。

    本文主要讲述uart通信过程。

    //主控Transmit

    				
    					void UART_Main_SendDirection(void)
    {
        main_txbuf[0] = 0xAA;
        main_txbuf[1] = 0x55;
    
        main_txbuf[2] = Motor_Direction[0];
        main_txbuf[3] = Motor_Direction[1];
        main_txbuf[4] = Motor_Direction[2];
        main_txbuf[5] = Motor_Direction[3];
    
        main_txbuf[6] = main_txbuf[2] + main_txbuf[3] + main_txbuf[4] + main_txbuf[5];
    
        HAL_UART_Transmit_DMA(&huart3, main_txbuf, 7);
    }
    
    				
    			

    此处在发送的数据包前添加了帧头0xAA 0x55,数据包尾部添加了求和计算

    //从控Receive

    				
    					void UART_Sub_RxParse(){
        uint8_t sub_rxbuf_temp[7];
    
        for(int i = 0; i < 7; i++)
        {
            if(sub_rxbuf[i] == 0xAA &#038;&#038; sub_rxbuf[i+1] == 0x55)
            {
                memcpy(sub_rxbuf_temp, sub_rxbuf, 7);
                for(int k = 0; k < i; k++)
                {
                    int temp = sub_rxbuf_temp[0];
                    for(int j = 1; j < 7; j++)
                    {
                        sub_rxbuf_temp[j-1] = sub_rxbuf_temp[j];
                    }
                    sub_rxbuf_temp[6] = temp;
                }
            }
    
                int8_t sum = sub_rxbuf_temp[2] + sub_rxbuf_temp[3] + sub_rxbuf_temp[4] + sub_rxbuf_temp[5];
                if(sum == sub_rxbuf_temp[6])
                {
                    Motor_Direction_From_Main[0] = sub_rxbuf_temp[2];
                    Motor_Direction_From_Main[1] = sub_rxbuf_temp[3];
                    Motor_Direction_From_Main[2] = sub_rxbuf_temp[4];
                    Motor_Direction_From_Main[3] = sub_rxbuf_temp[5];
            }
        }
    }
    				
    			

    为防止uart传输中的不稳定导致丢包等情况,此处定义了一个temp数组。使用memcpy复制到temp中,并采用for循环处理数组,以保证0xAA帧头位于数组第一位

    				
    					if(sub_rxbuf[i] == 0xAA && sub_rxbuf[i+1] == 0x55)
            {
                memcpy(sub_rxbuf_temp, sub_rxbuf, 7);
                for(int k = 0; k < i; k++)
                {
                    int temp = sub_rxbuf_temp[0];
                    for(int j = 1; j < 7; j++)
                    {
                        sub_rxbuf_temp[j-1] = sub_rxbuf_temp[j];
                    }
                    sub_rxbuf_temp[6] = temp;
                }
            }
    				
    			

    //从控Transmit

    				
    					void UART_Sub_Send_RealSpeed(void)
    {
        sub_txbuf[0] = 0xAA;
        sub_txbuf[1] = 0x55;
    
        // 将PID输出的 int16_t 转为字节流
        int16_t *speed = (int16_t*)Real_Speed;
    
        sub_txbuf[2] = speed[0] & 0xFF;
        sub_txbuf[3] = (speed[0] >> 8) & 0xFF;
    
        sub_txbuf[4] = speed[1] & 0xFF;
        sub_txbuf[5] = (speed[1] >> 8) & 0xFF;
    
        sub_txbuf[6] = speed[2] & 0xFF;
        sub_txbuf[7] = (speed[2] >> 8) & 0xFF;
    
        sub_txbuf[8] = speed[3] & 0xFF;
        sub_txbuf[9] = (speed[3] >> 8) & 0xFF;
    
        // 校验
        uint8_t sum = 0;
        for (int i = 2; i <= 9; i++)
            sum += sub_txbuf[i];
        sub_txbuf[10] = sum;
    
        HAL_UART_Transmit_DMA(&#038;huart1, sub_txbuf, 11);
    }
    
    				
    			

    此处依旧添加0xAA 0x55帧头和尾部求和校验。

    				
    					int16_t *speed = (int16_t*)Real_Speed;
    
        sub_txbuf[2] = speed[0] & 0xFF;
        sub_txbuf[3] = (speed[0] >> 8) & 0xFF;
    
        sub_txbuf[4] = speed[1] & 0xFF;
        sub_txbuf[5] = (speed[1] >> 8) & 0xFF;
    
        sub_txbuf[6] = speed[2] & 0xFF;
        sub_txbuf[7] = (speed[2] >> 8) & 0xFF;
    
        sub_txbuf[8] = speed[3] & 0xFF;
        sub_txbuf[9] = (speed[3] >> 8) & 0xFF;
    				
    			

    因编码器输出的Real_Speed已包含方向,但我们的uart通信协议只能传输uint8_t类型的指针数据,所以这里采用常用的位运算把数据处理为高八位和低八位的格式。

    //主控Receive

    				
    					void UART_Main_RxPrase(void)
    {
        uint8_t main_rxbuf_temp[11];
    
        for(int i = 0; i < 11; i++)
        {
            if(main_rxbuf[i] == 0xAA &#038;&#038; main_rxbuf[i+1] == 0x55)
            {
                memcpy(main_rxbuf_temp, main_rxbuf, 11);
                for(int k = 0; k < i; k++){
                    int temp = main_rxbuf_temp[0];
                    for (int j = 1; j < 11; j++){
                        main_rxbuf_temp[j-1] = main_rxbuf_temp[j];
                    }
                    main_rxbuf_temp[10] = temp;
                }
    
                uint8_t sum = main_rxbuf_temp[2] + main_rxbuf_temp[3] + main_rxbuf_temp[4] + main_rxbuf_temp[5] + main_rxbuf_temp[6] + main_rxbuf_temp[7] + main_rxbuf_temp[8] + main_rxbuf_temp[9];
                if(sum == main_rxbuf_temp[10])
                {
                    Real_Speed_From_Sub[0] = (int16_t)((main_rxbuf_temp[3] << 8) | main_rxbuf_temp[2]);
                    Real_Speed_From_Sub[1] = (int16_t)((main_rxbuf_temp[5] << 8) | main_rxbuf_temp[4]);
                    Real_Speed_From_Sub[2] = (int16_t)((main_rxbuf_temp[7] << 8) | main_rxbuf_temp[6]);
                    Real_Speed_From_Sub[3] = (int16_t)((main_rxbuf_temp[9] << 8) | main_rxbuf_temp[8]);
                }
            }
        }
    }
    				
    			

    此处原理同上,采用for循环处理帧头,使其保持在第一位。再用按位或运算还原带方向的Real_Speed数据。

    //总结

    uart数据传输中应充分考虑传输的不稳定性、不确定性。采用帧头帧尾设定与处理校验,保证每组接收的数据均为正确的。

    同时应注意有符号数据的高低八位传输。

    至此,两块c8t6的uart通信协议传输全部完成。

  • C语言隐式类型转换

    //引言

    近日笔者在学习C语言过程中意识到了隐式类型转换的重要性,故学习了解相关知识,用此文记录。

    //数据类型级别

    数据类型级别高低

    //什么是隐式类型转换

     在没有写 (type) 强制转换的情况下,编译器自动进行的一种类型转换行为。

    例如:

    int a = 3;

    double b = a; 

    此时,因为double类型的转换优先级高于int,变量a在b的赋值运算时,会被自动转换为double类型。

    //整型提升

    这是 C 语言中最常见的隐式转换。

    ▶ 规则:

    所有比 int 小的类型:

    char , unsigned char , short , unsigned short

    在运算时会 自动提升为 int 或 unsigned int。

    例如:

    char a = 5;

    char b = 6;

    char c = a + b; // a、b 都先提升为 int,再做运算

    a+b 的类型是 int,不是 char。

    ⚠ 可能导致溢出或精度问题:

    char x = 120;

    char y = 10;

    char z = x + y; // 120+10=130,溢出到 char 范围

    //运算中的隐式类型转换

    当表达式中出现不同类型时,C 会进行自动提升:

    ▶int + double

    int a = 10;

    double b = 3.5;

    double c = a + b; // a -> double

    ▶ float + double

    float x = 1.2f;

    double y = 2.3;

    double z = x + y; // x -> double

    ▶unsigned + signed 

    unsigned int a = 5;

    int b = -20;

    printf(“%d\n”, a + b);

    b 会自动转成 unsigned,变为极大正数

    //赋值时的隐式类型转换

    ▶ double -> int

    double a = 3.14;

    int b = a; // 小数部分丢失

    //常见问题

    ① 表达式中参与除法

    printf(“%f”, 5/2);

    两个都是 int → 计算结果是 int → 2
    最后转为 double → 2.000000

    不丢精度的写法应为:

    printf(“%f”, 5/2.0);  // 2.500000

    ② char 溢出

    char c = 130;
    printf(“%d\n”, c);   // -126(有符号 char 溢出)

    ③ uint 与 int 混算

    unsigned int a = 100;
    int b = -200;

    printf(“%d\n”, a+b);  // b 转无符号 → 巨大正数

    //总结

    显然,隐式类型转换并不是一个优秀的写法。在使用中应该避免隐式类型转换,多用显示类型转换。

    另外,如今大多数的编译器在编译时,都会warning隐式类型转换。

    在coding时应该养成避免这种写法的好习惯。