【 声明版权所有欢迎转载请勿用于商业用途。 联系信箱feixiaoxing 163.com】串口作为使用最多的方式在工业中有很多的应用场景。当然除了串口can用的也很多。我们一个一个来看。所谓的串口在mcu上一般称之为ttl。ttl通常是可以通过usb转ttl的模块被pc访问和使用的。但是由于历史和实际场景使用的原因工业上大部分都是用232、485和can。所以有了上一篇的gpio和串口基础就可以学习下面这三种接口。最后为了应用和调试的方便我们还稍微提了一下i2c接口的oled屏幕有兴趣的同学可以找点资料多学习一下。1、232接口所谓的232接口就是ttl经过电压转换芯片之后变成了232。转换之后tx还是txrx还是rx但是电压变了。早期的时候232用的非常多特别是工业场景。但是现在普遍被485和can代替。因此对于我们来说知道怎么回事其实就可以了。真的需要使用的时候弄一个电压转换芯片和db9接口很容易把ttl转成232接口。2、485接口生产生活中485把ttl转换成了差分信号这么一来传输的举例就增加了很多。而且485具有总线属性很多设备都可以挂在上面大大节省了mcu的pin。这样就可以把大部分的pin留给gpio无形之中降低了使用的难度。测试的时候一般需要两块stm32f103c8t6的板子a对ab对bgnd对gnd这样就算是接好了。测试的时候我们可以一个板子不停485发数据#include stm32f10x.h // Device header #define RS485_TX_EN() GPIO_SetBits(GPIOA, GPIO_Pin_4) // Switch to transmit mode #define RS485_RX_EN() GPIO_ResetBits(GPIOA, GPIO_Pin_4) // Switch to receive mode static void Delay_us(uint32_t xus) { SysTick-LOAD 72 * xus; SysTick-VAL 0x00; SysTick-CTRL 0x00000005; while(!(SysTick-CTRL 0x00010000)); SysTick-CTRL 0x00000004; } static void Delay_ms(uint32_t xms) { while(xms--) { Delay_us(1000); } } void USART2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // PA2 - TX GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA3 - RX GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_Init(GPIOA, GPIO_InitStructure); // PA4 - RS485 direction control GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); RS485_RX_EN(); // Default to receive mode USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART2, USART_InitStructure); USART_Cmd(USART2, ENABLE); } void USART2_SendByte(uint8_t byte) { USART_SendData(USART2, byte); while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); } void USART2_SendString(char *str) { RS485_TX_EN(); // Switch to transmit mode while (*str) { USART2_SendByte((uint8_t)*str); str; } while (USART_GetFlagStatus(USART2, USART_FLAG_TC) RESET); // Wait until fully shifted out RS485_RX_EN(); // Switch back to receive mode } int main(void) { uint32_t count 0; char buf[32]; USART2_Init(); while (1) { // Format a simple counter message, e.g. Hello 0000\r\n buf[0] H; buf[1] e; buf[2] l; buf[3] l; buf[4] o; buf[5] ; buf[6] 0 ((count / 1000) % 10); buf[7] 0 ((count / 100) % 10); buf[8] 0 ((count / 10) % 10); buf[9] 0 (count % 10); buf[10] \r; buf[11] \n; buf[12] \0; USART2_SendString(buf); count; Delay_ms(100); // Send every 500ms } }另外一个板子循环接收数据并且用ttl发出来这样就可以看到刚才板子发的内容了。#include stm32f10x.h // Device header #define RS485_TX_EN() GPIO_SetBits(GPIOA, GPIO_Pin_4) // Switch USART2 RS485 to transmit mode #define RS485_RX_EN() GPIO_ResetBits(GPIOA, GPIO_Pin_4) // Switch USART2 RS485 to receive mode /* ---------------- USART1 (PA9-TX, PA10-RX, standard TTL) ---------------- */ void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // PA9 - TX GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA10 - RX GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); } void USART1_SendByte(uint8_t byte) { USART_SendData(USART1, byte); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } /* ---------------- USART2 (PA2-TX, PA3-RX, RS485 via SP3485, PA4 DE/RE) ---------------- */ void USART2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // PA2 - TX GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA3 - RX GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_Init(GPIOA, GPIO_InitStructure); // PA4 - RS485 direction control (DE/RE tied together) GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); RS485_RX_EN(); // Default to receive mode USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART2, USART_InitStructure); USART_Cmd(USART2, ENABLE); } void USART2_SendByte(uint8_t byte) { RS485_TX_EN(); // Switch to transmit mode USART_SendData(USART2, byte); while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); // Wait until send buffer is empty while (USART_GetFlagStatus(USART2, USART_FLAG_TC) RESET); // Wait until the byte is fully shifted out RS485_RX_EN(); // Switch back to receive mode } /* ---------------- Main: bridge USART1 - USART2 ---------------- */ int main(void) { USART1_Init(); USART2_Init(); while (1) { // Data received on USART1 - forward to USART2 if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) ! RESET) { uint8_t byte USART_ReceiveData(USART1); USART2_SendByte(byte); } // Data received on USART2 - forward back to USART1 if (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) ! RESET) { uint8_t byte USART_ReceiveData(USART2); USART1_SendByte(byte); } } }有了485之后mcu本身通信的能力大大提高了可以做很多之前没有办法做的事情了。3、can接口弄懂了485的基本原理之后其实can也是类似的。尤其是现在具身智能非常火很多关节模组都是can接口开发的时候可以用串口转一下实际部署的时候还是要直接操作can接口的。can接口除了phy之外自身也是独立的一个协议。它不像ttl/232/485共用一个ipcan是独立的一个ip。我们使用的时候一般也是找两个板子即can h连接can hcan l连接can l最好gnd也连一下。这样首先准备一下发送端的代码是这样的#include stm32f10x.h // Device header #include stdio.h #include string.h /* Delay Functions */ static void Delay_us(uint32_t xus) { SysTick-LOAD 72 * xus; SysTick-VAL 0x00; SysTick-CTRL 0x00000005; while(!(SysTick-CTRL 0x00010000)); SysTick-CTRL 0x00000004; } static void Delay_ms(uint32_t xms) { while(xms--) { Delay_us(1000); } } /* USART1 (Debug output, PA9-TX / PA10-RX) */ void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // PA9 - TX GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA10 - RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); } void USART1_SendByte(uint8_t byte) { USART_SendData(USART1, byte); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } void USART1_SendString(char *str) { while (*str) { USART1_SendByte((uint8_t)*str); str; } } /* CAN1 (Remapped to PB8-RX / PB9-TX) */ void CAN1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; CAN_FilterInitTypeDef CAN_FilterInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // CAN1 remap: CAN_RX - PB8, CAN_TX - PB9 GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE); // PB8 - CAN_RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, GPIO_InitStructure); // PB9 - CAN_TX GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); CAN_DeInit(CAN1); CAN_StructInit(CAN_InitStructure); CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM ENABLE; CAN_InitStructure.CAN_AWUM DISABLE; CAN_InitStructure.CAN_NART DISABLE; CAN_InitStructure.CAN_RFLM DISABLE; CAN_InitStructure.CAN_TXFP DISABLE; CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_13tq; CAN_InitStructure.CAN_BS2 CAN_BS2_4tq; CAN_InitStructure.CAN_Prescaler 4; // 500kbps APB136MHz CAN_Init(CAN1, CAN_InitStructure); // Filter: accept all IDs CAN_FilterInitStructure.CAN_FilterNumber 0; CAN_FilterInitStructure.CAN_FilterMode CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_Filter_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation ENABLE; CAN_FilterInit(CAN_FilterInitStructure); } // Send a standard data frame, returns 1 on success, 0 on failure uint8_t CAN1_SendData(uint32_t id, uint8_t *data, uint8_t len) { CanTxMsg TxMessage; uint8_t mbox; uint32_t i 0; TxMessage.StdId id; TxMessage.ExtId 0; TxMessage.IDE CAN_ID_STD; TxMessage.RTR CAN_RTR_Data; TxMessage.DLC len; for (i 0; i len; i) { TxMessage.Data[i] data[i]; } mbox CAN_Transmit(CAN1, TxMessage); i 0; while ((CAN_TransmitStatus(CAN1, mbox) ! CANTXOK) (i 0xFFFFF)) { i; } return (i 0xFFFFF) ? 1 : 0; } /* main */ int main(void) { char buf[96]; uint16_t counter 0; uint8_t txData[8]; USART1_Init(); CAN1_Init(); USART1_SendString( CAN TX Demo Start (PB8/PB9, 500kbps) \r\n); while (1) { txData[0] (uint8_t)(counter 8); txData[1] (uint8_t)(counter 0xFF); txData[2] 0xAA; txData[3] 0xBB; txData[4] 0xCC; txData[5] 0xDD; txData[6] 0xEE; txData[7] 0xFF; if (CAN1_SendData(0x123, txData, 8)) { sprintf(buf, [TX] ID:0x123 DATA: %02X %02X %02X %02X %02X %02X %02X %02X\r\n, txData[0], txData[1], txData[2], txData[3], txData[4], txData[5], txData[6], txData[7]); } else { sprintf(buf, [TX] send failed!\r\n); } USART1_SendString(buf); counter; Delay_ms(500); // Send one frame every 500ms } }接着就是接收端的代码。对于接收端的代码通常就是收到之后转到usart1打印一下#include stm32f10x.h // Device header #include stdio.h #include string.h /* Delay Functions */ static void Delay_us(uint32_t xus) { SysTick-LOAD 72 * xus; SysTick-VAL 0x00; SysTick-CTRL 0x00000005; while(!(SysTick-CTRL 0x00010000)); SysTick-CTRL 0x00000004; } static void Delay_ms(uint32_t xms) { while(xms--) { Delay_us(1000); } } /* USART1 (Debug output, PA9-TX / PA10-RX) */ void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // PA9 - TX GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA10 - RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); } void USART1_SendByte(uint8_t byte) { USART_SendData(USART1, byte); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } void USART1_SendString(char *str) { while (*str) { USART1_SendByte((uint8_t)*str); str; } } /* CAN1 (Remapped to PB8-RX / PB9-TX) */ void CAN1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; CAN_FilterInitTypeDef CAN_FilterInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // CAN1 remap: CAN_RX - PB8, CAN_TX - PB9 GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE); // PB8 - CAN_RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, GPIO_InitStructure); // PB9 - CAN_TX GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); CAN_DeInit(CAN1); CAN_StructInit(CAN_InitStructure); CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM ENABLE; CAN_InitStructure.CAN_AWUM DISABLE; CAN_InitStructure.CAN_NART DISABLE; CAN_InitStructure.CAN_RFLM DISABLE; CAN_InitStructure.CAN_TXFP DISABLE; CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_13tq; CAN_InitStructure.CAN_BS2 CAN_BS2_4tq; CAN_InitStructure.CAN_Prescaler 4; // 500kbps APB136MHz, must match sender CAN_Init(CAN1, CAN_InitStructure); // Filter: accept all IDs CAN_FilterInitStructure.CAN_FilterNumber 0; CAN_FilterInitStructure.CAN_FilterMode CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_Filter_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation ENABLE; CAN_FilterInit(CAN_FilterInitStructure); } // Polling check for new data, returns 1 if received, 0 if no data pending uint8_t CAN1_ReceiveData(CanRxMsg *RxMessage) { if (CAN_MessagePending(CAN1, CAN_FIFO0) 0) { return 0; } CAN_Receive(CAN1, CAN_FIFO0, RxMessage); return 1; } /* main */ int main(void) { char buf[96]; CanRxMsg RxMessage; USART1_Init(); CAN1_Init(); USART1_SendString( CAN RX Demo Start (PB8/PB9, 500kbps) \r\n); USART1_SendString(Waiting for CAN messages...\r\n); while (1) { if (CAN1_ReceiveData(RxMessage)) { sprintf(buf, [RX] ID:0x%03X DLC:%d DATA: %02X %02X %02X %02X %02X %02X %02X %02X\r\n, (int)RxMessage.StdId, RxMessage.DLC, RxMessage.Data[0], RxMessage.Data[1], RxMessage.Data[2], RxMessage.Data[3], RxMessage.Data[4], RxMessage.Data[5], RxMessage.Data[6], RxMessage.Data[7]); USART1_SendString(buf); } // Polling mode, no delay needed - check for new messages as fast as possible } }此时串口接到接收端的stm32板子上如果一直有打印基本就算得上是can通信ok了。4、i2c接口的小屏幕很多工控场景也需要显示一些数据或者是为了调试使用也需要一个小屏幕。大部分时候选用一个i2c接口的0.96寸oled屏幕其实就可以。这方面的demo非常多这里就不赘述了。