STM32(三十)蓝牙通信
一、蓝牙模块参数简介
- 蓝牙模块:汇承HC05蓝牙串口通信模块。
- 蓝牙协议:Bluetooth Specification V2.0带EDR蓝牙协议。
- 无线工作频段为2.4GHz ISM。
- 调制方式是GFSK。
- 模块最大发射功率为4dBm。
- 接收灵敏度-85dBm。
- 板载PCB天线,可以实现10米距离通信。
- 自带LED灯,可直观判断蓝牙的连接状态。
- 模块采用CSR的BC417芯片,支持AT 指令。
- 用户可根据需要更改角色(主、从模式)以及串口波特率、设备名称等参数,使用灵活。
二、系列产品
三、工作原理简介
1、工作原理
如上图所示,HC-05 模块用于代替全双工通信时的物理连线。左边的设备向模块发送串口数据,模块的 RXD 端口收到串口数据后,自动将数据以无线电波的方式发送到空中。右边的模块能自动接收到,并从 TXD 还原最初左边设备所发的串口数据。从右到左也是一样的。
2、模块与单片机 MCU 等设备的连接
①:模块与供电系统为 3.3V 的 MCU 连接时,串口交叉连接即可(模块的 RX 接 MCU 的 TX、模块的 TX 接 MCU
的 RX)
②:模块与供电系统为 5V 的 MCU 连接时,可在模块的 RX 端串接一个 220R~1K 电阻再接 MCU 的 TX,模块的
TX 直接接 MCU 的 RX,无需串接电阻。(注:请先确认所使用的 MCU 把 3.0V 或以上电压认定为高电平,否则需
加上 3.3V/5V 电平转换电路)
注:模块的电源为 3.3V,不能接 5V, 5V 的电源必须通过 LDO 降压到 3.3V 后再给模块供电。
四、蓝牙模式及AT指令
1、AT指令模式
用于设置蓝牙的相关信息(名字,配对密码,波特率(9600))按下模块上的按键,上电,即可进行AT指令模式,led灯慢闪表示进入AT模式,双闪表示蓝牙已连接并打开了端口。
2、数据透传模式
上电后就进入了数据透传模式,此时蓝牙模块led灯快闪,连接后led灯双闪。在此模式下连接后可以传输数据。
3、常用的AT指令
①测试:AT\r\n 返回:OK (即通信成功)
②设置蓝牙名称:AT+NAME=PDD\r\n 返回:OK
③查询蓝牙名称:AT+NAME?\r\n 返回:+NAME=PDD OK
④设置配对密码:AT+PSWD=1234\r\n 返回:OK
⑤查询配对密码:AT+PSWD?\r\n 返回:+PSWD=1234 OK
⑥查询设备mac地址:AT+ADDR?\r\n 返回:+ADDR:21:13:52b9b OK
五、蓝牙调试
手机可以下载蓝牙调试器或者关注蓝牙调试公众号,蓝牙模块在数据透传模式可以用来连接蓝牙模块,并且给蓝牙模块发送数据,也可以通过编辑按钮,按按钮发送数据给蓝牙模块,蓝牙模块收到数据后通过串口发给MCU,从而控制MCU的一些外设
六、STM32F407ZET6与蓝牙模块连接
如上图U转UART1的RXD和TXD通过CH340芯片连接USB,可以通过USB连接电脑,用串口工具给MCU发数据和接收数据,USART1_TX和UASRT1_RX是和主芯片连着,调帽跳到1-3、2-4。如果蓝牙要和MCU通信,则调帽3-5、4-6相连,此时连到P4,将蓝牙模块的RXD和P4的TXD1连接,TXD和P4的RXD1连接,此时蓝牙模块就可以就可以和MCU通信.
实验一:通过手机连接蓝牙控制蜂鸣器的响灭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | //uart .c文件 #include "uart.h" void Uart_Init(uint32_t band) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //1 、初始化硬件 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_25MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_Init(GPIOA, &GPIO_InitStruct); // 复用为串口1 GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); USART_InitStruct.USART_BaudRate = band; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件控制流 USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx ; USART_InitStruct.USART_Parity= USART_Parity_No; // 无奇偶校验 USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_Init(USART1, &USART_InitStruct); // 初始化NVIC NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x1; // 抢占优先级 0-3 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x1; // 响应占优先级 0-3 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; // 中断向量号 TIM3 中断向量号位tim3 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能 NVIC_Init(&NVIC_InitStruct); // 串口中断 USART_ITConfig(USART1, USART_IT_RXNE,ENABLE); USART_Cmd(USART1,ENABLE); } //main 函数 #include "stdio.h" uint16_t uart1_recv_data; // 重定向fputc fputs fgetc fgets int fputc(int ch,FILE *f) { USART_SendData(USART1, ch); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); return ch; } // 串口进入中断接收蓝牙发过来的数据 void USART1_IRQHandler() { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { // 清楚中断标志位 往里面写1 记住一定要清空 USART_ClearITPendingBit(USART1,USART_IT_RXNE); uart1_recv_data = USART_ReceiveData(USART1); } } // 给蓝牙模块发数据 void USART_SendString(const uint8_t* str) { u8 i = 0; while (*(str+i)!= '\n' ) { USART_SendData(USART1, *(str+i)); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); i++; } } int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 给中断优先级分组 LED_Init(); Key_Init(); Beep_Init(); Uart_Init(9600); // 和蓝牙模块的波特率一致 printf ( "hello" ); while (1) { if (uart1_recv_data == '1' ) { PFout(8) =1; } if (uart1_recv_data == '0' ) { PFout(8) =0; } delay_s(1); } return 0; } //uart .c文件 #include "uart.h" void Uart_Init(uint32_t band) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //1 、初始化硬件 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_25MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_Init(GPIOA, &GPIO_InitStruct); // 复用为串口1 GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); USART_InitStruct.USART_BaudRate = band; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件控制流 USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx ; USART_InitStruct.USART_Parity= USART_Parity_No; // 无奇偶校验 USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_Init(USART1, &USART_InitStruct); // 初始化NVIC NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x1; // 抢占优先级 0-3 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x1; // 响应占优先级 0-3 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; // 中断向量号 TIM3 中断向量号位tim3 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能 NVIC_Init(&NVIC_InitStruct); // 串口中断 USART_ITConfig(USART1, USART_IT_RXNE,ENABLE); USART_Cmd(USART1,ENABLE); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | //main 函数 ------通过手机连接蓝牙控制蜂鸣器的响灭。 #include "stdio.h" uint16_t uart1_recv_data; // 重定向fputc fputs fgetc fgets int fputc(int ch,FILE *f) { USART_SendData(USART1, ch); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); return ch; } // 串口进入中断接收蓝牙发过来的数据 void USART1_IRQHandler() { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { // 清楚中断标志位 往里面写1 记住一定要清空 USART_ClearITPendingBit(USART1,USART_IT_RXNE); uart1_recv_data = USART_ReceiveData(USART1); } } // 给蓝牙模块发数据 void USART_SendString(const uint8_t* str) { u8 i = 0; while (*(str+i)!= '\n' ) { USART_SendData(USART1, *(str+i)); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); i++; } } int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 给中断优先级分组 LED_Init(); Key_Init(); Beep_Init(); Uart_Init(9600); // 和蓝牙模块的波特率一致 printf ( "hello" ); while (1) { if (uart1_recv_data == '1' ) { PFout(8) =1; } if (uart1_recv_data == '0' ) { PFout(8) =0; } delay_s(1); } return 0; } |
实验二、usart1连接电脑串口,usart3连接蓝牙模块,串口调试工具和蓝牙模块通信,串口工具发命令给usart1,usart1发给usart3到蓝牙模块。(注意stm32Fxx.h修改晶振频率为8M)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | #include "stm32f4xx.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #include "stm32f4xx_usart.h" #include "stdio.h" static GPIO_InitTypeDef GPIO_InitStructure; static USART_InitTypeDef USART_InitStructure; static NVIC_InitTypeDef NVIC_InitStructure; static uint8_t g_usart1_recv_buf[128]={0}; static uint8_t g_usart1_recv_cnt = 0; // 重定义fputc函数 int fputc(int ch, FILE *f) { USART_SendData(USART1,ch); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); return ch; } void delay_us(int nus) // 微秒 { //SystemCoreClock SysTick->LOAD = (SystemCoreClock /8/1000000 ) * nus; // 定时时间 SysTick->CTRL |= 1; // 开启定时器,开始计数 while ((SysTick->CTRL & (1<<16)) == 0); // 等待定时时间到 SysTick->CTRL &=~1; // 关闭定时器 } void delay_ms(int nms) // 毫秒 { uint32_t m,n; m = nms /500 ; n = nms%500; while (m--) { SysTick->LOAD = (SystemCoreClock /8/1000 ) * 500; // 定时时间 SysTick->CTRL |= 1; // 开启定时器,开始计数 while ((SysTick->CTRL & (1<<16)) == 0); // 等待定时时间到 SysTick->CTRL &=~1; // 关闭定时器 } if (n) { SysTick->LOAD = (SystemCoreClock /8/1000 ) * n; // 定时时间 SysTick->CTRL |= 1; // 开启定时器,开始计数 while ((SysTick->CTRL & (1<<16)) == 0); // 等待定时时间到 SysTick->CTRL &=~1; // 关闭定时器 } } void LED_Init(void) { // 使能GPIOE,GPIOF时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF, ENABLE); //GPIOF9 ,F10初始化设置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //LED0 和LED1对应IO口 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 普通输出模式, GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出,驱动LED需要电流驱动 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉 GPIO_Init(GPIOF, &GPIO_InitStructure); // 初始化GPIOF,把配置的数据写入寄存器 //GPIOE13 ,PE14初始化设置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14; //LED2 和LED3对应IO口 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 普通输出模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉 GPIO_Init(GPIOE, &GPIO_InitStructure); // 初始化GPIOE,把配置的数据写入寄存器 GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10); //GPIOF9 ,PF10设置高,灯灭 GPIO_SetBits(GPIOE,GPIO_Pin_13 | GPIO_Pin_14); } void USART1_Init(uint32_t baud) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // 使能USART1时钟 // 串口1对应引脚复用映射 GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9 复用为USART1 GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10 复用为USART1 //USART1 端口配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9 与GPIOA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 速度50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉 GPIO_Init(GPIOA,&GPIO_InitStructure); // 初始化PA9,PA10 //USART1 初始化设置 USART_InitStructure.USART_BaudRate = baud; // 波特率设置 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字长为8位数据格式 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_Rx | USART_Mode_Tx; // 收发模式 USART_Init(USART1, &USART_InitStructure); // 初始化串口1 USART_Cmd(USART1, ENABLE); // 使能串口1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启相关中断 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 串口1中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; // 抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能 NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化VIC寄存器 } void USART3_Init(uint32_t baud) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /* GPIOB Configuration: PB10 PB11 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOB, &GPIO_InitStructure); /* Connect USART3_TX pins to PB10 */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3); /* Connect USART3_RX pins to PB11 */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3); /* Enable USART3 clock */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); USART_InitStructure.USART_BaudRate = baud; 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_Rx | USART_Mode_Tx; USART_Init(USART3, &USART_InitStructure); /* Enable the USARTx Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Enable USART3 */ USART_Cmd(USART3, ENABLE); /* Enable the Rx buffer empty interrupt */ USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); } void USART1_SendBytes(uint8_t *pbuf,uint32_t len) { while (len--) { USART_SendData(USART1,*pbuf++); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); } } void USART1_SendString(uint8_t *pstr) { while (pstr && *pstr) { USART_SendData(USART1,*pstr++); while (USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); } } void USART3_SendBytes(uint8_t *pbuf,uint32_t len) { while (len--) { USART_SendData(USART3,*pbuf++); while (USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET); } } void USART3_SendString(uint8_t *pstr) { while (pstr && *pstr) { USART_SendData(USART3,*pstr++); while (USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET); } } int main(void) { LED_Init(); // 系统定时器初始化,时钟源来自HCLK,且进行8分频, // 系统定时器时钟频率=168MHz /8 =21MHz SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 设置中断优先级分组2 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 串口1,波特率115200bps,开启接收中断 USART1_Init(115200); // 串口3,波特率9600bps,开启接收中断 USART3_Init(38400); while (1) { } } void USART1_IRQHandler(void) // 串口1中断服务程序 { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 接收中断 { // 从串口1接收数据 g_usart1_recv_buf[g_usart1_recv_cnt]=USART_ReceiveData(USART1); USART_SendData(USART1, g_usart1_recv_buf[g_usart1_recv_cnt]); // 记录多少个数据 g_usart1_recv_cnt++; // 检测到换行符或接收的数据满的时候则发送数据 if (g_usart1_recv_buf[g_usart1_recv_cnt-1]== '\n' || g_usart1_recv_cnt>=(sizeof g_usart1_recv_buf)-1) { USART3_SendBytes(g_usart1_recv_buf,g_usart1_recv_cnt); USART1_SendBytes(g_usart1_recv_buf,g_usart1_recv_cnt); g_usart1_recv_cnt = 0; } } } void USART3_IRQHandler(void) { uint8_t d; /* USART in Receiver mode */ if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET) { d=USART_ReceiveData(USART3); USART1_SendBytes(&d,1); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)