大千世界
无奇不有
-
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 && 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(&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 && 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时应该养成避免这种写法的好习惯。