【下载链接】
[Windows版UARTDFUv1.0]
链接:https://pan.baidu.com/s/1uIOV712tmQjW-zmbKTPvGg
提取码:3qpt
[安卓版UARTDFUv1.0]
链接:https://blog.csdn.net/ZLK1214/article/details/119111672
[适用于STM32F103和STM32F107的Bootloader]
链接:https://blog.csdn.net/ZLK1214/article/details/119087488
[适用于STM32F405RG的Bootloader]
链接:https://blog.csdn.net/ZLK1214/article/details/119062947
[其他Bootloader]
想要其他芯片的欢迎在评论区留言。
【工具说明】
此工具采用C语言开发(纯C语言实现上位机,没有类,全都是函数),开放源代码,开发工具为Visual Studio 2010, 编译出来的程序可在Windows XP及以上的系统中运行。
(也可以用更高版本的Visual Studio编译,但是版本太高的话,编译出来的程序就无法在XP系统中运行了)
支持下载hex格式的程序。选择好hex格式的程序文件后,可以看到程序的大小,程序的Flash地址范围,程序的入口地址。
程序可通过HC-05或CC2541蓝牙串口下载到STM32单片机中,当然直接用有线串口(USB转串口模块,或RS232线)下载也是可以的。
兼容性:
(1)带热缩管的3.6~6V电压范围的蓝色底板HC-05模块(3.3V供电不能工作)
(2)不带热缩管的3.2~6V电压范围的绿色底板HC-05模块(3.3V供电可以工作)
(3)不带热缩管的BT05-A蓝色小CC2541F256模块(3.3V供电可以工作)
(4)带热缩管的蓝色大CC2541F256模块,6针排针引出,线序跟HC-05相同(3.3V供电可以工作)
模块 | 蓝牙名称长度 | 蓝牙密码长度 | 备注 |
---|---|---|---|
1 | 1~32 | 1~8 | 蓝牙名称可包含汉字,但不能以汉字结尾,否则最后一个汉字会乱码 |
2 | 1~32 | 1~16 | 1. 蓝牙名称不能含有汉字 2. 不能用AT+NAME?指令查询蓝牙名称 3. 不支持AT+INQ或AT+SCAN指令搜索蓝牙设备 |
3 | 1~18 | 6 | |
4 | 1~20 | 6 | |
编码为UTF-8,一个汉字算3个字符 |
测试发现,模块3和4虽然都是CC2541F256,但是他们的AT命令却有差别。
比如,模块3支持AT+ADDR和AT+LADDR命令,获取到的蓝牙地址相同,且主从模式下都能获取蓝牙名称和地址。但模块4不支持AT+ADDR命令,而且只有在从模式下才能通过AT+LADDR命令获取到蓝牙地址,AT+NAME名称也只能在从模式下获取。还有,模块3发送AT+BAUD8后返回的是+BAUD=8,但模块4返回的却是+BAUD=115200。模块3的AT命令不区分大小写,但模块4严格区分大小写,AT不能写成at,否则串口无回应。经测试,模块3和模块4可以互连。
HC-05蓝牙模块的特点:
(1)体积较大,程序下载速度快。
(2)带蓝牙功能的笔记本电脑可以直接连接,但连接蓝牙串口后必须要保持连接1~2秒以上才能关闭,否则蓝牙模块有很大的概率死机,再也无法连接。
关闭蓝牙串口后需要等1~2秒后才能再次打开,否则蓝牙模块不会死机,但提示串口打开失败。
(3)模块上带有KEY按键。
松开KEY键给蓝牙模块通电,进入的是通信模式,波特率可调,此时不能响应AT指令。
如果配置的是主机模式,则通电后自动连接另一个蓝牙模块。两个HC-05蓝牙模块相连,配对密码必须相同,才能连接成功。蓝牙模块主动连接电脑时,电脑上打开传入(Incoming)COM口即可收发串口数据。
如果配置的是从机模式,则等待其他蓝牙设备连接。电脑主动连接蓝牙模块,应打开传出(Outgoing)COM口。
按住KEY键给蓝牙模块通电,进入的是AT模式,波特率固定为38400,可以响应AT指令,但不能通信。要想通信,必须要松开KEY键重新上电。
(4)搜索蓝牙设备时可以搜出蓝牙名称和蓝牙地址。
(5)有些模块的供电电压是3.6~6V,有些是3.2V~6V,所以不是所有的模块都支持3.3V供电电压。
CC2541蓝牙模块的特点:
(1)体积较小,但是程序下载速度慢,一秒钟只能传输500字节数据。
(2)带蓝牙功能的笔记本电脑不能直接连接,必须额外插上USB转串口模块+CC2541模块。
(3)模块上没有KEY按键,上电后进入的就是AT模式,用AT+CONN命令连接另一个CC2541蓝牙模块,进入通信模式。AT模式和通信模式的波特率相同且可调。要退出通信模式,只能重新上电。
(4)搜索蓝牙设备时只能搜出蓝牙地址。
(5)模块的供电电压为3.3V。
安卓手机上可选择Serial Bluetooth Terminal软件连接蓝牙模块,查看串口调试信息。
在BLUETOOTH CLASSIC选项卡中可以搜索并连接HC-05蓝牙模块。
在BLUETOOTH LE选项卡中可以搜索并连接CC2541蓝牙模块。
【软件使用说明】
注意:为了简单起见,程序里面将蓝牙模块通信模式下的波特率写死了,固定为115200。使用新蓝牙模块,配置蓝牙时会询问是否将波特率改为115200,如果不改就不能继续后续操作。想要使用其他波特率,那就必须修改上位机的C语言源代码。
Tera Term串口调试工具的命令:
"C:\Program Files (x86)\teraterm\ttermpro.exe" /C=%d /SPEED=115200
(方案一)用笔记本电脑自带的蓝牙功能连接开发板上的HC-05蓝牙模块
1. 用四根杜邦线将一个USB转串口模块和HC-05蓝牙模块连接在一起(按下表),按住KEY按键插入电脑USB口。使用软件的蓝牙配置功能(串口号选择USB转串口模块),配置好蓝牙名称和配对密码,搜索设备,选中“空设备”并连接(将模块配置为从机模式)。
USB转串口模块 | HC-05蓝牙模块 |
---|---|
+5V | VCC |
GND | GND |
RXD | TXD |
TXD | RXD |
提示:有的HC-05蓝牙模块不支持“AT+NAME?”查询,因此无法查询出蓝牙设备名称,只能用“AT+NAME=XXX”设置蓝牙名称。
2. 拔出USB转串口模块,取下HC-05蓝牙模块,将HC-05蓝牙模块插到开发板上,确保开发板里面已用STLINK调试器(或JLINK调试器)烧写好bootloader程序。
3. 在电脑上添加蓝牙设备,搜索HC-05蓝牙模块,输入密码并配对,完成后会增加两个COM口。一个是传入(Incoming),另一个是传出(Outgoing)。
3. 在软件里面选择好要下载的hex文件,勾选上“连接最小保持时间”的复选框,毫秒值为2000。串口号选择传出COM口,点击下载按钮,即可下载程序到开发板的Application区域。
4. 点击“复位并运行固件“按钮,即可开始运行程序,并自动打开串口调试工具,查看蓝牙串口printf输出。
(方案二)电脑上插入USB转串口模块+HC-05蓝牙模块,连接插在开发板上的另一个HC-05蓝牙模块
1. 把开发板上的蓝牙模块取下来,用杜邦线将USB转串口模块和开发板上的这个蓝牙模块连接在一起,按住KEY按键插入电脑USB口。使用软件的蓝牙配置功能(串口号选择USB转串口模块),配置好蓝牙名称和配对密码,搜索设备,选中“空设备”并连接(将模块配置为从机模式)。
2. 把USB转串口模块和蓝牙模块拔下来,将蓝牙模块插回开发板。
3. 再拿另外一个蓝牙模块,用杜邦线连接到USB转串口模块上后,按住KEY按键插入电脑USB口。使用软件的蓝牙配置功能(串口号选择USB转串口模块),配置好蓝牙名称和配对密码,配对密码一定要和开发板上的蓝牙模块相同。搜索设备,找到开发板上的蓝牙名称并连接。
4. 拔出USB转串口模块,松开蓝牙模块上的KEY按键重新插入电脑USB口。
5. 在软件里面选择好要下载的hex文件,取消勾选“连接最小保持时间”的复选框。串口号选择USB转串口模块,点击下载按钮,即可下载程序到开发板的Application区域。
6. 点击“复位并运行固件“按钮,即可开始运行程序,并自动打开串口调试工具,查看蓝牙串口printf输出。
注意:这是一个不可靠的蓝牙传输信道,传输大块数据会发生字节传输错误、字节丢失的情况,导致出现Data CRC failed和Data timeout错误。不过不用担心,bootloader程序具有重传机制,重传出错的数据包。
如果发现上位机一直显示“请按下复位键”,无法开始下载程序,则说明bootloader程序main函数的dfu_cnt变量初值太小,将其调大就能解决问题。另外,还可以调大dfu_sync函数的HAL_UART_Receive超时时间。传输信道越不可靠,这个值就应该设得越大。数值越大,上位机检测到设备的成功率越高,但开机后程序启动越慢。
(方案三)电脑上插入USB转串口模块+CC2541蓝牙模块,连接插在开发板上的另一个CC2541蓝牙模块
1. 把开发板上的蓝牙模块取下来,用杜邦线将USB转串口模块和开发板上的这个蓝牙模块连接在一起,插入电脑USB口。使用软件的蓝牙配置功能(串口号选择USB转串口模块),点击搜索设备,记录下窗口下方显示的本设备的(不是搜出来的其他设备的)蓝牙设备地址。蓝牙名称和配对密码不用管。
2. 把USB转串口模块和蓝牙模块拔下来,将蓝牙模块插回开发板。
3. 再拿另外一个蓝牙模块,用杜邦线连接到USB转串口模块上后,插入电脑USB口。使用软件的蓝牙配置功能(串口号选择USB转串口模块),搜索设备,找到开发板上的蓝牙地址并连接。窗口下方的蓝牙名称和配对密码也不用管。
4. 在软件里面选择好要下载的hex文件,取消勾选“连接最小保持时间”的复选框。串口号选择USB转串口模块,点击下载按钮,即可下载程序到开发板的Application区域。
5. 点击“复位并运行固件“按钮,即可开始运行程序,并自动打开串口调试工具,查看蓝牙串口printf输出。
注意:这是一个不可靠的蓝牙传输信道,传输大块数据会发生字节丢失的情况,导致出现Data timeout错误。不过不用担心,bootloader程序具有重传机制,重传出错的数据包。
【工作原理】
本工具把STM32的Flash空间分成了三个区域:Bootloader、Reserved和Application。
Bootloader区域存放是单片机复位后运行的第一个程序,负责接收蓝牙模块的程序数据和指令,烧写程序,并跳转到Application程序区运行。
Reserved区域用于保存程序的大小和CRC校验和,Application区域保存烧写的程序。
Bootloader和Application是两个不同的程序。Bootloader是固定不变的程序,采用HAL库编写。Application是通过蓝牙烧写的程序,除了HAL库外,还可以采用标准库,或者纯寄存器方式编写。Bootloader跳转到Application区执行前,会调用HAL_DeInit()函数,关闭所有的外设和中断,因此Application程序的运行不会受到Bootloader的影响(除了SysTick和IWDG)。因此,如果Application程序不是用HAL库写的,而是标准库或者纯寄存器操作,那么就必须要声明一个空的void SysTick_Handler(void)函数。
【关于字符编码】
蓝牙串口采用的字符编码为UTF-8,但Windows系统里面采用的编码是UTF-16。
UTF-8和UTF-16的区别:字符编码值相同,只是存储方式不同。UTF-8每个字符所占的字节数为1~4,属于变长存储。UTF-16绝大多数字符(基本多语言平面的字符)占2字节,属于定长存储。
另外,像GB2312这类特定语言的字符集统称为ANSI。
GetWindowText是一个宏,用于获取文本框输入的文字内容。根据项目配置(_UNICODE宏是否定义),GetWindowText宏展开后,要么是GetWindowTextA函数,要么是GetWindowTextW函数。Edit_GetText宏和GetWindowText宏等价。
GetWindowTextA函数获取到的字符编码为ANSI,在简体中文版Windows系统下是GB2312编码,使用字符数组的类型为char []。
GetWindowTextW函数获取到的字符编码为UTF-16,使用的字符数组的类型为wchar_t []。
char []字符数组可以存放任意编码的字符串。对于char str[] = "简体中文",源文件是什么编码,str字符数组就以什么编码保存字符串。
wchar_t []字符数组专门用来存放UTF-16编码的字符串。对于wchar_t wstr[] = L"简体中文",无论源文件是什么编码,wstr字符数组都是UTF-16编码。如果源文件不是UTF-16编码,编译器在编译时,会从源文件的字符串编码转换到UTF-16编码。
ANSI或UTF-8转UTF-16使用的函数是MultiByteToWideChar,反过来则使用WideCharToMultiByte。可以先获取转换后字符串的长度,用malloc函数分配好空间,再开始转换。
类型 | 未定义_UNICODE | 定义了_UNICODE |
---|---|---|
TCHAR | char | wchar_t |
LPSTR | char * | |
LPCSTR | const char * | |
LPWSTR | wchar_t * | |
LPCWSTR | const wchar_t * | |
LPTSTR | char * | wchar_t * |
LPCTSTR | const char * | const wchar_t * |
给TCHAR字符数组赋值要用TEXT()宏:TCHAR tstr[] = TEXT("简体中文")
【程序主要代码】
main.c:
#include
#include
#include
#include
#include
#include
#include
#include "bluetooth.h"
#include "hexfile.h"
#include "port.h"
#include "taskbar.h"
#include "resource.h"
#include "UARTDFU.h"
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' language='*' publicKeyToken='6595b64144ccf1df'\"")
static void browse_files(void);
static DWORD CALLBACK browse_files_thread(LPVOID lpParameter);
static uint8_t calc_crc8(const void *data, int len);
static BOOL confirm_cancel(void);
static void download_firmware(uintptr_t header);
static DWORD CALLBACK download_firmware_process(LPVOID lpParameter);
static void enable_controls(BOOL enabled);
static int load_commports(void);
static void set_progress(int value);
static void update_fileinfo(const HexFile *hexfile);
static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
#if ADVANCED_MODE
static void load_configuration(void);
static void reset_and_run(int reset);
static DWORD CALLBACK reset_and_run_process(LPVOID lpParameter);
static void save_configuration(void);
#endif
HINSTANCE hinstMain;
HWND hwndCheck[2], hwndCombo, hwndSpin;
TCHAR szMessage[500];
static int downloading;
static BOOL file_loaded;
static HANDLE hThread;
static HWND hwndButton[7], hwndEdit[3], hwndDlg, hwndProgress;
#if ADVANCED_MODE
static int defport;
static BOOL conf_changed;
#endif
/* 弹出选择固件文件的对话框 */
static void browse_files(void)
{
// GetOpenFileNameA函数不能在COINIT_MULTITHREADED环境下运行, 否则XP系统下“我的电脑”显示不出任何内容
// 新建一个线程单独运行,可解决这个问题
CreateThread(NULL, 0, browse_files_thread, NULL, 0, NULL);
}
static DWORD CALLBACK browse_files_thread(LPVOID lpParameter)
{
char name[MAX_PATH];
int ret;
BOOL bret;
HexFile hexfile;
OPENFILENAMEA ofn = {0};
name[0] = '\0';
ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
ofn.hInstance = hinstMain;
ofn.hwndOwner = hwndDlg;
ofn.lStructSize = sizeof(OPENFILENAMEA);
ofn.lpstrDefExt = "hex";
ofn.lpstrFile = name;
#if ADVANCED_MODE
ofn.lpstrFilter = "HEX文件 (*.hex)\0*.HEX\0所有文件 (*.*)\0*.*\0";
#else
ofn.lpstrFilter = "固件文件 (*.hex)\0*.HEX\0";
#endif
ofn.nMaxFile = sizeof(name);
bret = GetOpenFileNameA(&ofn);
if (bret)
{
ret = HexFile_Load(&hexfile, name);
if (ret == 0)
{
file_loaded = TRUE;
update_fileinfo(&hexfile);
HexFile_Free(&hexfile);
}
else
{
MessageBox(hwndDlg, TEXT("读取文件失败!"), TEXT("打开文件"), MB_ICONWARNING);
file_loaded = FALSE;
update_fileinfo(NULL);
}
SetWindowTextA(hwndEdit[0], name);
}
return bret;
}
/* 计算CRC8校验码 */
static uint8_t calc_crc8(const void *data, int len)
{
const uint8_t *p = data;
int i, j;
uint16_t temp = 0;
if (len != 0)
temp = p[0] << 8;
for (i = 1; i <= len; i++)
{
if (i != len)
temp |= p[i];
for (j = 0; j < 8; j++)
{
if (temp & 0x8000)
temp ^= POLYNOMIAL_CRC8 << 7;
temp <<= 1;
}
}
return temp >> 8;
}
/* 确认取消下载 */
static BOOL confirm_cancel(void)
{
if (downloading == 2)
return MessageBox(hwndDlg, TEXT("您确定要取消固件下载吗?\n取消后设备将不能正常启动,但可以重新下载固件。"), TEXT("取消下载"), MB_ICONQUESTION | MB_OKCANCEL) == IDOK;
else
return TRUE;
}
/* 下载固件 */
static void download_firmware(uintptr_t header)
{
if (downloading)
{
if (confirm_cancel())
{
downloading = 0;
EnableWindow(hwndButton[0], FALSE);
#if ADVANCED_MODE
EnableWindow(hwndButton[5], FALSE);
#endif
}
}
else
{
downloading = 1;
szMessage[0] = '\0';
enable_controls(FALSE);
hThread = CreateThread(NULL, 0, download_firmware_process, (LPVOID)header, 0, NULL);
}
}
/* 下载固件的线程 */
static DWORD CALLBACK download_firmware_process(LPVOID lpParameter)
{
char filename[MAX_PATH];
double progress;
int i, j, ret;
uint8_t buffer[16];
uint8_t crc;
uintptr_t header = (uintptr_t)lpParameter;
COMMTIMEOUTS timeouts = {0};
DeviceResponse resp;
FirmwareInfo info;
HANDLE hPort;
HexFile hexfile;
#if ADVANCED_MODE
CRCConfig crccfg;
#endif
// 初始化COM (用于设置任务栏图标上的进度条)
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// 改为取消按钮
if (header == HEADER_FIRMWARE_INFO)
{
Button_SetText(hwndButton[0], TEXT("取消"));
EnableWindow(hwndButton[0], TRUE);
}
#if ADVANCED_MODE
else if (header == HEADER_CRC_CONFIG)
{
Button_SetText(hwndButton[5], TEXT("取消"));
EnableWindow(hwndButton[5], TRUE);
}
#endif
// 打开串口
set_progress(-1);
hPort = open_port(-1);
if (hPort == INVALID_HANDLE_VALUE)
goto end;
// 设置超时时间
timeouts.ReadTotalTimeoutConstant = 80;
timeouts.WriteTotalTimeoutConstant = 10000;
SetCommTimeouts(hPort, &timeouts);
// 检测设备
#if ADVANCED_MODE
SetDlgItemText(hwndDlg, IDC_STATIC5, TEXT("请按下复位键"));
#else
SetDlgItemText(hwndDlg, IDC_STATIC5, TEXT("正在检测设备..."));
#endif
while (downloading)
{
memset(buffer, 0xab, sizeof(buffer));
write_port(hPort, buffer, sizeof(buffer));
ret = read_port(hPort, buffer, sizeof(buffer));
if (ret == sizeof(buffer))
{
for (j = 0; j < sizeof(buffer); j++)
{
if (buffer[j] != 0xcd)
break;
}
if (j == sizeof(buffer))
break;
}
}
if (!downloading)
goto end;
set_progress(0);
timeouts.ReadTotalTimeoutConstant = 10000;
SetCommTimeouts(hPort, &timeouts);
switch (header)
{
case HEADER_FIRMWARE_INFO:
// 读取HEX文件并发送HEX文件信息
GetWindowTextA(hwndEdit[0], filename, sizeof(filename));
ret = HexFile_Load(&hexfile, filename);
if (ret == -1)
{
lstrcpy(szMessage, TEXT("读取固件文件失败!"));
break;
}
update_fileinfo(&hexfile);
info.header = HEADER_FIRMWARE_INFO;
info.size = hexfile.size;
info.start_addr = hexfile.start_addr;
info.end_addr = hexfile.end_addr;
info.entry_point = hexfile.entry_point;
info.firmware_checksum = calc_crc8(hexfile.data, hexfile.size);
info.header_checksum = calc_crc8(&info, sizeof(info) - 1);
write_port(hPort, &info, sizeof(info));
while (downloading)
{
ret = read_port(hPort, &resp, sizeof(resp));
if (ret != sizeof(resp) || calc_crc8(&resp, sizeof(resp)) != 0)
{
lstrcpy(szMessage, TEXT("下载固件时发生错误!"));
break;
}
else if (resp.addr == 0)
{
_stprintf_s(szMessage, _countof(szMessage), TEXT("固件大小超过设备Flash容量: %.1lfKB"), resp.size / 1024.0);
break;
}
else if (resp.addr == 0xffffffff && resp.size == 0xffffffff)
{
// 重新发送固件信息
write_port(hPort, &info, sizeof(info));
continue;
}
else if (downloading == 1 && resp.addr != hexfile.start_addr)
{
#if ADVANCED_MODE
_stprintf_s(szMessage, _countof(szMessage), TEXT("固件地址无效!\n请将Keil MDK项目属性中Target选项卡下IROM1的值设为0x%08x,\n并将system_*.c中的VECT_TAB_OFFSET改为0x%04xU,\n然后重新编译固件。"), resp.addr, resp.addr - 0x08000000);
#else
lstrcpy(szMessage, TEXT("该固件不适用于此设备!"));
#endif
break;
}
else if (resp.size == 0)
break; // 发送结束
else if (resp.size == 0xffffffff)
continue; // 继续等待 (比如擦除Flash需要很长时间, 为了避免提示超时, 可使用这个指令)
else if (resp.addr - hexfile.start_addr + resp.size > hexfile.size)
{
lstrcpy(szMessage, TEXT("设备出现异常!"));
break;
}
if (downloading == 1)
{
downloading = 2;
#if ADVANCED_MODE
Button_SetCheck(hwndCheck[0], TRUE);
#endif
}
write_port(hPort, hexfile.data + (resp.addr - hexfile.start_addr), resp.size);
progress = (double)(resp.addr - hexfile.start_addr + resp.size) / hexfile.size;
i = (int)(progress * 100 + 0.5);
if (i > 100)
i = 100;
set_progress(i);
crc = calc_crc8(hexfile.data + (resp.addr - hexfile.start_addr), resp.size);
write_port(hPort, &crc, 1);
}
HexFile_Free(&hexfile);
#if !ADVANCED_MODE
// 下载完成后启动固件
buffer[0] = 0xab;
write_port(hPort, buffer, 1);
#endif
break;
#if ADVANCED_MODE
case HEADER_CRC_CONFIG:
crccfg.header = HEADER_CRC_CONFIG;
crccfg.crc_enabled = Button_GetCheck(hwndCheck[0]);
crccfg.header_checksum = calc_crc8(&crccfg, sizeof(crccfg) - 1);
do
{
write_port(hPort, &crccfg, sizeof(crccfg));
ret = read_port(hPort, &resp, sizeof(resp));
if (ret != sizeof(resp) || calc_crc8(&resp, sizeof(resp)) != 0)
resp.size = 0; // 回应内容有误, 视为失败
} while (downloading && resp.addr == 0xffffffff && resp.size == 0xffffffff); // 判断是否重传crccfg
if (resp.size != 1)
lstrcpy(szMessage, TEXT("修改CRC配置失败!"));
break;
#endif
}
end:
close_port(hPort);
if (hwndDlg != NULL)
PostMessage(hwndDlg, WM_USER, 1, header);
CoUninitialize();
return 0;
}
/* 启用或禁用控件 */
static void enable_controls(BOOL enabled)
{
EnableWindow(hwndButton[0], enabled && file_loaded);
EnableWindow(hwndButton[1], enabled);
EnableWindow(hwndButton[2], enabled);
EnableWindow(hwndButton[4], enabled);
EnableWindow(hwndCheck[1], enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC13), enabled);
EnableWindow(hwndCombo, enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC9), enabled);
EnableWindow(hwndEdit[2], enabled && Button_GetCheck(hwndCheck[1]));
InvalidateRect(hwndSpin, NULL, FALSE);
#if ADVANCED_MODE
EnableWindow(hwndButton[3], enabled);
EnableWindow(hwndButton[5], enabled);
EnableWindow(hwndButton[6], enabled);
EnableWindow(hwndCheck[0], enabled);
Edit_SetReadOnly(hwndEdit[1], !enabled);
#endif
}
/* 从注册表中读取串口列表 */
static int load_commports(void)
{
#if ADVANCED_MODE
int selection;
#endif
DWORD index, type, namelen, valuelen;
HKEY key;
LSTATUS status;
TCHAR name[50];
TCHAR value[50];
ComboBox_ResetContent(hwndCombo);
status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &key);
if (status != ERROR_SUCCESS)
return -1;
index = 0;
while (1)
{
namelen = _countof(name);
valuelen = sizeof(value);
status = RegEnumValue(key, index, name, &namelen, NULL, &type, (LPBYTE)value, &valuelen);
if (status != ERROR_SUCCESS)
break;
ComboBox_AddString(hwndCombo, value);
index++;
}
if (index != 0)
{
#if ADVANCED_MODE
_stprintf_s(value, _countof(value), TEXT("COM%d"), defport);
selection = ComboBox_FindString(hwndCombo, 0, value);
if (selection != -1)
ComboBox_SetCurSel(hwndCombo, selection);
else
#endif
ComboBox_SetCurSel(hwndCombo, 0);
}
RegCloseKey(key);
return 0;
}
/* 读取配置信息 */
#if ADVANCED_MODE
static void load_configuration(void)
{
char path[MAX_PATH];
char *p, *q, *r;
int n, size;
FILE *fp;
HANDLE module;
LRESULT range;
module = GetModuleHandle(NULL);
GetModuleFileNameA(module, path, MAX_PATH);
p = strrchr(path, '\\');
if (p == NULL)
return;
*p = '\0';
SetCurrentDirectoryA(path);
size = MAX_PATH - (int)(p - path) - 1;
strncat_s(p, size, "\\conf.txt", size);
p[size] = '\0';
fopen_s(&fp, path, "r");
if (fp == NULL)
return;
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
p = (char *)malloc(size + 1);
if (p != NULL)
{
size = (int)fread(p, 1, size, fp);
p[size] = '\0';
q = strchr(p, '\n');
if (q != NULL)
{
*q = '\0';
q++;
r = strchr(q, '\n');
if (r != NULL)
{
*r = '\0';
n = atoi(r + 1); // 正数表示勾选了, 负数表示没有勾选, 绝对值等于延时的毫秒数
if (n > 0)
{
Button_SetCheck(hwndCheck[1], TRUE);
EnableWindow(hwndEdit[2], TRUE);
InvalidateRect(hwndSpin, NULL, FALSE);
}
else if (n < 0)
n = -n;
range = SendMessage(hwndSpin, UDM_GETRANGE, 0, 0);
if (n >= HIWORD(range) && n <= LOWORD(range))
SendMessage(hwndSpin, UDM_SETPOS, 0, n);
}
SetWindowTextA(hwndEdit[1], q);
}
if (_strnicmp(p, "com", 3) == 0)
defport = atoi(p + 3);
free(p);
}
fclose(fp);
conf_changed = FALSE;
}
#endif
#if ADVANCED_MODE
/* 复位并运行固件 */
static void reset_and_run(int reset)
{
int len;
LPTSTR cmdfmt;
if (!downloading)
{
len = Edit_GetTextLength(hwndEdit[1]) + 2;
cmdfmt = (LPTSTR)malloc(len * sizeof(TCHAR));
if (cmdfmt != NULL)
{
cmdfmt[0] = (reset) ? 'Y' : 'N';
Edit_GetText(hwndEdit[1], cmdfmt + 1, len - 1);
downloading = 1;
szMessage[0] = '\0';
enable_controls(FALSE);
hThread = CreateThread(NULL, 0, reset_and_run_process, cmdfmt, 0, NULL);
if (hThread == NULL)
free(cmdfmt);
}
}
}
/* 发送复位命令的线程 */
static DWORD CALLBACK reset_and_run_process(LPVOID lpParameter)
{
int err = 0;
int i, len, port;
uint32_t data;
BOOL ret;
COMMTIMEOUTS timeouts = {0};
HANDLE hPort;
LPTSTR cmdfmt = (LPTSTR)lpParameter + 1;
LPTSTR cmd;
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
CoInitializeEx(NULL, COINIT_MULTITHREADED);
len = lstrlen(cmdfmt) + 50;
cmd = (LPTSTR)malloc(len * sizeof(TCHAR));
if (cmd != NULL)
{
set_progress(-1);
port = get_port();
if (*(cmdfmt - 1) == 'Y')
{
// 复位
hPort = open_port(port);
if (hPort != INVALID_HANDLE_VALUE)
{
timeouts.WriteTotalTimeoutConstant = 10000;
SetCommTimeouts(hPort, &timeouts);
data = 0xabababab;
wait_port(); // 先等待
i = write_port(hPort, &data, 4); // 后复位
if (i != 4)
{
err = 1;
lstrcpy(szMessage, TEXT("复位失败!"));
}
close_port(hPort); // 复位后马上关串口, 准备启动串口调试工具
wait_port();
}
else
err = 1;
}
if (!err)
{
_sntprintf_s(cmd, len, len, cmdfmt, port);
for (i = 0; cmd[i] != '\0'; i++)
{
if (cmd[i] != ' ' && cmd[i] != '\t')
break;
}
if (cmd[i] != '\0') // 命令不为空
{
si.cb = sizeof(STARTUPINFO);
ret = CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (!ret)
lstrcpy(szMessage, TEXT("启动串口调试工具失败!\n指定的串口调试工具可能未安装。"));
}
}
free(cmd);
}
free(lpParameter);
if (hwndDlg != NULL)
PostMessage(hwndDlg, WM_USER, 2, 0);
CoUninitialize();
return 0;
}
/* 保存配置 */
static void save_configuration(void)
{
char *str;
int i, n, len;
FILE *fp;
HWND list[2] = {hwndCombo, hwndEdit[1]};
fopen_s(&fp, "conf.txt", "w");
if (fp != NULL)
{
for (i = 0; i < _countof(list); i++)
{
len = GetWindowTextLengthA(list[i]) + 1;
str = (char *)malloc(len);
if (str != NULL)
{
GetWindowTextA(list[i], str, len);
fprintf(fp, "%s\n", str);
free(str);
}
else
fprintf(fp, "\n");
}
n = (int)SendMessage(hwndSpin, UDM_GETPOS, 0, 0);
if (!Button_GetCheck(hwndCheck[1]))
n = -n;
fprintf(fp, "%d\n", n);
fclose(fp);
conf_changed = FALSE;
}
}
#endif
/* 设置进度条进度 */
static void set_progress(int value)
{
FLASHWINFO flash;
LONG_PTR style;
TCHAR str[20];
style = GetWindowLongPtr(hwndProgress, GWL_STYLE);
if (value == -1)
{
// 循环滚动
if ((style & PBS_MARQUEE) == 0)
{
SetWindowLongPtr(hwndProgress, GWL_STYLE, style | PBS_MARQUEE);
SendMessage(hwndProgress, PBM_SETMARQUEE, 1, 0);
}
Taskbar_SetProgress(hwndDlg, TBPF_INDETERMINATE, 0, 0);
}
else
{
if (style & PBS_MARQUEE)
{
SendMessage(hwndProgress, PBM_SETMARQUEE, 0, 0);
SetWindowLongPtr(hwndProgress, GWL_STYLE, style & ~PBS_MARQUEE);
}
if (value >= 0 && value <= 100)
{
// 正常进度
SendMessage(hwndProgress, PBM_SETPOS, value, 0);
Taskbar_SetProgress(hwndDlg, TBPF_NORMAL, value, 100);
}
else if (value == 101)
{
// 完成
value = 100;
SendMessage(hwndProgress, PBM_SETPOS, 100, 0);
Taskbar_SetProgress(hwndDlg, TBPF_NORMAL, 100, 100);
flash.cbSize = sizeof(FLASHWINFO);
flash.dwFlags = FLASHW_TIMERNOFG | FLASHW_TRAY; // 一直闪烁到用户切换回窗口
flash.dwTimeout = 0;
flash.hwnd = hwndDlg;
flash.uCount = 3;
FlashWindowEx(&flash); // 任务栏图标闪烁 (黄色)
}
else if (value == 102)
{
// 清除任务栏进度
Taskbar_SetProgress(hwndDlg, TBPF_NOPROGRESS, 0, 0);
}
else if (value == -2)
{
// 错误
Taskbar_SetProgress(hwndDlg, TBPF_ERROR, 1, 1);
}
}
if (value == 0)
SetDlgItemText(hwndDlg, IDC_STATIC5, TEXT("已检测到设备"));
else if (value > 0 && value <= 100)
{
_stprintf_s(str, _countof(str), TEXT("进度: %d%%"), value);
SetDlgItemText(hwndDlg, IDC_STATIC5, str);
}
}
/* 显示固件文件信息 */
static void update_fileinfo(const HexFile *hexfile)
{
TCHAR str[50];
if (hexfile == NULL)
{
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT(""));
SetDlgItemText(hwndDlg, IDC_STATIC3, TEXT(""));
SetDlgItemText(hwndDlg, IDC_STATIC4, TEXT(""));
}
else
{
if (hexfile->size < 1024)
_stprintf_s(str, _countof(str), TEXT("%uB"), hexfile->size);
else if (hexfile->size < 1048576)
_stprintf_s(str, _countof(str), TEXT("%.2fKB (%u字节)"), hexfile->size / 1024.0f, hexfile->size);
else
_stprintf_s(str, _countof(str), TEXT("%.2fMB (%u字节)"), hexfile->size / 1048576.0f, hexfile->size);
SetDlgItemText(hwndDlg, IDC_STATIC2, str);
_stprintf_s(str, _countof(str), TEXT("0x%08x - 0x%08x"), hexfile->start_addr, hexfile->end_addr - 1);
SetDlgItemText(hwndDlg, IDC_STATIC3, str);
_stprintf_s(str, _countof(str), TEXT("0x%08x"), hexfile->entry_point);
SetDlgItemText(hwndDlg, IDC_STATIC4, str);
}
EnableWindow(hwndButton[0], hexfile != NULL);
}
/* 对话框消息处理 */
static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PNMLINK pnmLink;
switch (uMsg)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDC_BUTTON1:
browse_files();
break;
case IDC_BUTTON2:
load_commports();
break;
case IDC_BUTTON4:
DialogBox(hinstMain, MAKEINTRESOURCE(IDD_DIALOG3), hDlg, Bluetooth_DlgProc);
break;
case IDC_CHECK2:
EnableWindow(hwndEdit[2], Button_GetCheck(hwndCheck[1]));
InvalidateRect(hwndSpin, NULL, FALSE); // 重绘数值调整按扭 (兼容XP系统)
#if ADVANCED_MODE
conf_changed = TRUE;
#endif
break;
#if ADVANCED_MODE
case IDC_BUTTON3:
reset_and_run(1);
break;
case IDC_BUTTON5:
download_firmware(HEADER_CRC_CONFIG);
break;
case IDC_BUTTON6:
reset_and_run(0);
break;
case IDC_COMBO1:
if (wmEvent == CBN_EDITCHANGE || wmEvent == CBN_SELCHANGE)
conf_changed = TRUE;
break;
case IDC_EDIT2:
case IDC_EDIT3:
if (wmEvent == EN_CHANGE)
conf_changed = TRUE;
break;
#endif
case IDOK:
download_firmware(HEADER_FIRMWARE_INFO);
break;
case IDCANCEL:
// 点击了对话框关闭按钮
if (confirm_cancel())
{
#if ADVANCED_MODE
if (conf_changed)
save_configuration();
#endif
hwndDlg = NULL;
EndDialog(hDlg, 0);
}
break;
}
break;
case WM_INITDIALOG:
hwndDlg = hDlg;
hwndButton[0] = GetDlgItem(hDlg, IDOK);
hwndButton[1] = GetDlgItem(hDlg, IDC_BUTTON1);
hwndButton[2] = GetDlgItem(hDlg, IDC_BUTTON2);
hwndButton[4] = GetDlgItem(hDlg, IDC_BUTTON4);
hwndCheck[1] = GetDlgItem(hDlg, IDC_CHECK2);
hwndCombo = GetDlgItem(hDlg, IDC_COMBO1);
hwndEdit[0] = GetDlgItem(hDlg, IDC_EDIT1);
hwndEdit[2] = GetDlgItem(hDlg, IDC_EDIT3);
hwndProgress = GetDlgItem(hDlg, IDC_PROGRESS1);
hwndSpin = GetDlgItem(hDlg, IDC_SPIN1);
SendMessage(hwndSpin, UDM_SETRANGE, 0, MAKELPARAM(30000, 1));
SendMessage(hwndSpin, UDM_SETPOS, 0, 2000); // 默认延时2秒
#if ADVANCED_MODE
hwndButton[3] = GetDlgItem(hDlg, IDC_BUTTON3);
hwndButton[5] = GetDlgItem(hDlg, IDC_BUTTON5);
hwndButton[6] = GetDlgItem(hDlg, IDC_BUTTON6);
hwndCheck[0] = GetDlgItem(hDlg, IDC_CHECK1);
Button_SetCheck(hwndCheck[0], TRUE);
hwndEdit[1] = GetDlgItem(hDlg, IDC_EDIT2);
#endif
lParam = (LPARAM)LoadImage(hinstMain, MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
SendMessage(hDlg, WM_SETICON, ICON_BIG, lParam);
lParam = (LPARAM)LoadImage(hinstMain, MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
SendMessage(hDlg, WM_SETICON, ICON_SMALL, lParam);
SendMessage(hwndProgress, PBM_SETRANGE, 0, MAKELPARAM(1, 100));
SetDlgItemText(hDlg, IDC_STATIC5, DEFAULT_TEXT);
update_fileinfo(NULL);
#if ADVANCED_MODE
load_configuration();
#endif
load_commports();
break;
case WM_NOTIFY:
pnmLink = (PNMLINK)lParam;
if (pnmLink->hdr.code == NM_CLICK && pnmLink->hdr.idFrom == IDC_SYSLINK1)
MessageBox(hDlg, SAMPLE_CODE, TEXT("示例代码"), 0);
break;
case WM_USER:
/* 串口线程即将退出时要执行的代码 */
if (wParam >= 1 && wParam <= 2)
{
switch (wParam)
{
case 1:
/* 下载完毕 */
if (downloading)
{
if (szMessage[0] != '\0')
set_progress(-2);
else
set_progress(101);
}
switch (lParam)
{
case HEADER_FIRMWARE_INFO:
if (downloading)
{
if (szMessage[0] == '\0')
MessageBox(hDlg, TEXT("固件下载完毕!"), TEXT("下载固件"), MB_ICONINFORMATION);
else
MessageBox(hDlg, szMessage, TEXT("下载固件"), MB_ICONWARNING);
}
Button_SetText(hwndButton[0], TEXT("下载"));
break;
#if ADVANCED_MODE
case HEADER_CRC_CONFIG:
if (downloading)
{
if (szMessage[0] == '\0')
MessageBox(hDlg, TEXT("修改CRC配置成功!"), TEXT("CRC配置"), MB_ICONINFORMATION);
else
MessageBox(hDlg, szMessage, TEXT("CRC配置"), MB_ICONWARNING);
}
Button_SetText(hwndButton[5], TEXT("配置CRC校验"));
break;
#endif
}
SetDlgItemText(hDlg, IDC_STATIC5, DEFAULT_TEXT);
break;
case 2:
/* 复位并运行固件完毕 */
if (szMessage[0] != '\0')
{
set_progress(-2);
MessageBox(hDlg, szMessage, TEXT("运行"), MB_ICONWARNING);
}
break;
}
downloading = 0;
enable_controls(TRUE);
set_progress(102);
}
break;
}
return 0;
}
/* 主函数 */
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
LRESULT ret;
hinstMain = hInstance;
InitCommonControls();
CoInitializeEx(NULL, COINIT_MULTITHREADED);
#if ADVANCED_MODE
ret = DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
#else
ret = DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG2), NULL, DlgProc);
#endif
if (hThread != NULL)
{
downloading = 0;
WaitForSingleObject(hThread, INFINITE);
}
CoUninitialize();
return (int)ret;
}
bluetooth.c:
#include
#include
#include
#include
#include
#include
#include "bluetooth.h"
#include "encoding.h"
#include "port.h"
#include "resource.h"
#pragma comment(lib, "uxtheme.lib")
static int add_device(const BluetoothDevice *device);
static DWORD CALLBACK bluetooth_process(LPVOID lpParameter);
static void copy_hc05_addr(char *dest, int destsize, const char *src);
static void dlg_init(void);
static void enable_controls(BOOL enabled);
static void start_bluetooth_process(uintptr_t opcode);
extern HINSTANCE hinstMain;
extern TCHAR szMessage[500];
static int searching_devices, testing_baudrate;
static int thread_active;
static HANDLE hThread;
static HIMAGELIST hImageList;
static HWND hwndButton[4], hwndDlg, hwndEdit[2], hwndList;
static int add_device(const BluetoothDevice *device)
{
char buf[100];
wchar_t wbuf[100];
LVITEMW lvi;
lvi.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_TEXT; // 设置图标、自定义参数和文本
if (device != NULL)
{
if (device->name[0] == '\0')
_snprintf_s(buf, sizeof(buf), sizeof(buf), "%s", device->addr); // 只显示蓝牙地址
else
_snprintf_s(buf, sizeof(buf), sizeof(buf), "%s (%s)", device->name, device->addr); // 显示蓝牙名称和地址
utf8to16_wbuf(buf, wbuf, sizeof(wbuf)); // UTF8转UTF16
lvi.iImage = 0; // 蓝牙图标
}
else
{
wcscpy_s(wbuf, _countof(wbuf), L"空设备 (用于回到从机模式)");
lvi.iImage = 1; // 无图标
}
lvi.iItem = 0; // 暂时先插入到开头, 等待自动排序 (控件已启用自动排序)
lvi.iSubItem = 0; // 第一列
lvi.lParam = (device != NULL) ? device->param : -1; // 设备序号 (连接时使用)
lvi.pszText = wbuf;
lvi.iItem = (int)SendMessage(hwndList, LVM_INSERTITEMW, 0, (LPARAM)&lvi); // 返回值为排序后的行号
if (device != NULL && device->rssi_valid)
{
_snwprintf_s(wbuf, _countof(wbuf), _countof(wbuf), L"%d", device->rssi);
lvi.mask = LVIF_TEXT; // 只设置文本
lvi.iSubItem = 1; // 第二列
lvi.pszText = wbuf;
SendMessage(hwndList, LVM_SETITEMW, 0, (LPARAM)&lvi);
}
return lvi.iItem;
}
static DWORD CALLBACK bluetooth_process(LPVOID lpParameter)
{
const int baudrate_list[] = {38400, 115200, 57600, 19200, 9600, 4800, 2400, 0}; // HC-05蓝牙模块AT模式下的波特率固定为38400, 所以要放第一个
char addr[20];
char buffer[200];
char *p, *q;
int baudrate[3] = {0}; // 三个下标分别表示波特率、停止位和校验位
int i, j, n;
int msgtype = 1;
wchar_t *ws;
uintptr_t opcode = (uintptr_t)lpParameter;
BluetoothDevice device;
BOOL ishc05 = FALSE, hc05_baudok = FALSE;
COMMTIMEOUTS timeouts = {0};
DCB dcb;
HANDLE hPort;
LVITEMA lvi;
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT("正在打开串口..."));
hPort = open_port(-1); // 打开串口, 若出错则会将错误消息填入szMessage
if (hPort == INVALID_HANDLE_VALUE)
{
msgtype = 2;
goto end;
}
dcb.DCBlength = sizeof(DCB);
GetCommState(hPort, &dcb);
/* 波特率匹配 */
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT("正在匹配波特率..."));
timeouts.ReadTotalTimeoutConstant = 500;
timeouts.WriteTotalTimeoutConstant = 1000; // 蓝牙串口必须设置发送超时时间, 避免因蓝牙设备不存在而无限等待
SetCommTimeouts(hPort, &timeouts);
testing_baudrate = 1;
for (i = 0; baudrate_list[i] != 0; i++)
{
if (dcb.BaudRate != baudrate_list[i])
{
dcb.BaudRate = baudrate_list[i];
SetCommState(hPort, &dcb);
}
for (j = 0; j < 2; j++)
{
if (!testing_baudrate)
{
lstrcpy(szMessage, TEXT("操作已取消"));
goto end;
}
write_port_str(hPort, "AT\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 200);
if (strcmp(buffer, "OK") == 0)
break;
}
if (j != 2)
break;
}
testing_baudrate = 0;
baudrate[0] = baudrate_list[i];
if (baudrate[0] == 0)
{
// 找不到合适的波特率, 或者已有主机连上使得AT指令不可用
msgtype = 2;
lstrcpy(szMessage, TEXT("蓝牙串口不能响应AT指令!\n若使用的是HC-05模块,必须在按住KEY按键的情况下通电,否则无法进入AT模式。\n波特率多次匹配失败后,HC-05模块有可能死机,可尝试模块重新上电。\n若使用的是CC2541模块,则可能是因为蓝牙已连接上设备。\n若想要更换设备连接,请拔下蓝牙串口设备再重新插上。"));
goto end;
}
else if (baudrate[0] == 38400)
{
// 判断是否为HC-05模块
read_port_str(hPort, buffer, sizeof(buffer)); // 吸收掉所有未读完的数据
write_port_str(hPort, "AT+UART?\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+UART:", 6) == 0)
{
n = sscanf_s(buffer, "+UART:%d,%d,%d", &baudrate[0], &baudrate[1], &baudrate[2]);
hc05_baudok = (n == 3 && baudrate[0] == 115200 && baudrate[1] == 0 && baudrate[2] == 0);
read_port_line(hPort, buffer, sizeof(buffer), 0);
ishc05 = (strcmp(buffer, "OK") == 0);
}
}
/* 配置波特率 */
if ((!ishc05 && baudrate[0] != 115200) || (ishc05 && !hc05_baudok))
{
if (!ishc05)
_sntprintf_s(szMessage, _countof(szMessage), _countof(szMessage), TEXT("当前蓝牙串口的波特率为%d。\n是否需要修改为115200?"), baudrate[0]);
else
_sntprintf_s(szMessage, _countof(szMessage), _countof(szMessage), TEXT("当前蓝牙串口的通信模式的波特率配置为“%d,%d,%d”。\n是否需要修改为“115200,0,0”?\n\n注意: HC-05模块的AT模式波特率固定为38400。\n这里指的是通信模式下,也就是不按住KEY按键给模块上电时的波特率。"), baudrate[0], baudrate[1], baudrate[2]);
if (MessageBox(hwndDlg, szMessage, TEXT("蓝牙串口"), MB_ICONQUESTION | MB_OKCANCEL) == IDOK)
szMessage[0] = '\0';
else
{
lstrcpy(szMessage, TEXT("串口波特率不正确"));
goto end;
}
if (!ishc05)
{
write_port_str(hPort, "AT+BAUD8\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "+BAUD=8") == 0 || strcmp(buffer, "+BAUD=115200") == 0)
{
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
dcb.BaudRate = CBR_115200;
SetCommState(hPort, &dcb);
}
}
if (dcb.BaudRate != CBR_115200)
{
lstrcpy(szMessage, TEXT("配置串口波特率失败"));
goto end;
}
for (i = 0; i < 10; i++)
{
write_port_str(hPort, "AT+BAUD\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 200);
if (strcmp(buffer, "+BAUD=8") == 0)
break;
}
if (i == 10)
{
lstrcpy(szMessage, TEXT("串口波特率配置成功但没有生效"));
goto end;
}
}
else
{
write_port_str(hPort, "AT+UART=115200,0,0\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") != 0)
{
lstrcpy(szMessage, TEXT("配置串口波特率失败"));
goto end;
}
}
}
/* 执行命令 */
timeouts.ReadTotalTimeoutConstant = (!ishc05) ? 25 : 300;
SetCommTimeouts(hPort, &timeouts);
switch (opcode)
{
case 1:
/* 搜索设备 */
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT("正在搜索设备..."));
if (!ishc05)
{
i = 10;
write_port_str(hPort, "AT+ROLE0\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "+ROLE=0") == 0)
{
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
// 等待生效
for (i = 0; i < 10; i++)
{
write_port_str(hPort, "AT+ROLE\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 200);
Sleep(500); // 延时, 以免命令没反应 (时间一定要够, 不然后面获取蓝牙名称会为空)
if (strcmp(buffer, "+ROLE=0") == 0)
break;
}
}
}
if (i == 10)
{
lstrcpy(szMessage, TEXT("配置串口从模式失败"));
break;
}
// 经测试, 某些CC2541蓝牙模块只有在从模式下才能获取到蓝牙名称和地址
write_port_str(hPort, "AT+NAME\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 200); // 最后一个参数不能是0, 以免下一行的命令字符串被设置成设备的蓝牙名称, 结果蓝牙名称以\r\n开头
if (strncmp(buffer, "+NAME=", 6) == 0)
{
ws = utf8to16(buffer + 6);
if (ws != NULL)
{
SetWindowTextW(hwndEdit[0], ws);
free(ws);
}
}
write_port_str(hPort, "AT+LADDR\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+LADDR=", 7) == 0)
SetDlgItemTextA(hwndDlg, IDC_STATIC4, buffer + 7);
write_port_str(hPort, "AT+PIN\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+PIN=", 5) == 0)
SetWindowTextA(hwndEdit[1], buffer + 5);
// 搜索前需要先切换到主模式
i = 10;
write_port_str(hPort, "AT+ROLE1\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "+ROLE=1") == 0)
{
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
// 等待生效
for (i = 0; i < 10; i++)
{
write_port_str(hPort, "AT+ROLE\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 200);
Sleep(500); // 延时, 以免命令没反应 (时间也一定要够, 不然后面无法响应搜索命令)
if (strcmp(buffer, "+ROLE=1") == 0)
break;
}
}
}
if (i == 10)
{
lstrcpy(szMessage, TEXT("配置串口主模式失败"));
break;
}
// 开始搜索
write_port_str(hPort, "AT+INQ\r\n");
searching_devices = 1;
while (read_port_line_cancellable(hPort, buffer, sizeof(buffer), 15000, &searching_devices))
{
if (strncmp(buffer, "+INQ:", 5) == 0)
{
p = strchr(buffer, ' ');
if (p == NULL)
p = strchr(buffer, '=');
if (p != NULL)
{
*p = '\0';
memset(&device, 0, sizeof(device));
device.param = atoi(buffer + 5);
p += 3;
for (i = 0; i < 6; i++)
{
device.addr[3 * i] = p[2 * i];
device.addr[3 * i + 1] = p[2 * i + 1];
device.addr[3 * i + 2] = (i == 5) ? '\0' : ':';
}
if (p[12] == ' ')
{
p += 13;
q = strchr(p, ' ');
if (q != NULL)
*q = '\0';
device.rssi_valid = TRUE;
device.rssi = atoi(p);
}
add_device(&device);
}
}
else if (strcmp(buffer, "+INQE") == 0)
{
read_port_line(hPort, buffer, sizeof(buffer), 250); // 有可能会收到“Devices Found”
break;
}
}
}
else
{
write_port_str(hPort, "AT+NAME?\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+NAME:", 6) == 0)
{
ws = utf8to16(buffer + 6);
if (ws != NULL)
{
SetWindowTextW(hwndEdit[0], ws);
free(ws);
}
}
read_port_line(hPort, buffer, sizeof(buffer), 0); // 接收"OK"这一行
write_port_str(hPort, "AT+ADDR?\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+ADDR:", 6) == 0)
{
copy_hc05_addr(addr, sizeof(addr), buffer + 6);
SetDlgItemTextA(hwndDlg, IDC_STATIC4, addr);
}
read_port_line(hPort, buffer, sizeof(buffer), 0);
write_port_str(hPort, "AT+PSWD?\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+PSWD:", 6) == 0)
SetWindowTextA(hwndEdit[1], buffer + 6);
read_port_line(hPort, buffer, sizeof(buffer), 0);
// 开始搜索
write_port_str(hPort, "AT+INQ\r\n");
searching_devices = 1;
while (read_port_line_cancellable(hPort, buffer, sizeof(buffer), 15000, &searching_devices))
{
if (strncmp(buffer, "+INQ:", 5) == 0)
{
memset(&device, 0, sizeof(device));
copy_hc05_addr(device.addr, sizeof(device.addr), buffer + 5);
p = strchr(buffer, ',');
if (p != NULL)
{
p = strchr(p + 1, ',');
if (p != NULL)
{
p++;
q = strchr(p, ',');
if (q != NULL)
{
*q = '\0';
strncpy_s(device.name, sizeof(device.name), q + 1, sizeof(device.name) - 1);
}
device.rssi_valid = TRUE;
sscanf_s(p, "%x", &device.rssi);
device.rssi = (short)device.rssi;
add_device(&device);
}
}
}
else if (strcmp(buffer, "OK") == 0)
break;
else if (strncmp(buffer, "ERROR", 5) == 0)
{
searching_devices = 0;
lstrcpy(szMessage, TEXT("设备搜索失败"));
goto end;
}
}
// 停止搜索
write_port_str(hPort, "AT+INQC\r\n");
read_port_str(hPort, buffer, sizeof(buffer));
}
if (searching_devices)
{
searching_devices = 0;
lstrcpy(szMessage, TEXT("设备搜索完毕"));
}
else
lstrcpy(szMessage, TEXT("操作已取消"));
break;
case 2:
/* 连接设备 */
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT("正在连接设备..."));
lvi.mask = LVIF_PARAM | LVIF_TEXT; // 获取参数和文本
lvi.iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED); // 选中行的行号
lvi.iSubItem = 0;
if (!ishc05)
{
lvi.pszText = addr + 2; // 存放文本的缓冲区
lvi.cchTextMax = sizeof(addr) - 2; // 缓冲区的大小
SendMessage(hwndList, LVM_GETITEMA, 0, (LPARAM)&lvi);
if (lvi.lParam != -1)
{
// 在主机模式下连接设备
addr[0] = '0';
addr[1] = 'x';
for (i = 1; i < 6; i++)
{
addr[2 * i + 2] = addr[3 * i + 2];
addr[2 * i + 3] = addr[3 * i + 3];
}
addr[14] = '\0';
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "AT+CONN%d\r\n", (int)lvi.lParam);
write_port_str(hPort, buffer);
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+Connecting", 11) == 0 && strcmp(buffer + 13, addr) == 0)
{
read_port_line(hPort, buffer, sizeof(buffer), 10000);
if (strncmp(buffer, "+Connected", 10) == 0 && strcmp(buffer + 12, addr) == 0)
{
msgtype = 4;
lstrcpy(szMessage, TEXT("连接设备成功!"));
break;
}
}
else if (strcmp(buffer, "OK") == 0)
{
msgtype = 4;
lstrcpy(szMessage, TEXT("模块已开始连接设备,请打开串口查看是否连接成功。\n若设备不在附近,模块会一直尝试连接,直到设备出现在附近并连接成功。"));
break;
}
}
else
{
// 回到从机模式
write_port_str(hPort, "AT+ROLE0\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "+ROLE=0") == 0)
{
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
msgtype = 4;
lstrcpy(szMessage, TEXT("已配置为从机模式。"));
break;
}
}
lstrcpy(szMessage, TEXT("配置串口从模式失败"));
break;
}
}
else
{
lvi.pszText = buffer;
lvi.cchTextMax = sizeof(buffer);
SendMessage(hwndList, LVM_GETITEMA, 0, (LPARAM)&lvi);
if (lvi.lParam == 0)
{
// 设为主机模式
n = (int)strlen(buffer);
if (buffer[n - 1] == ')')
{
buffer[n - 1] = '\0';
p = &buffer[n - 18];
}
else
p = buffer;
addr[0] = p[0];
addr[1] = p[1];
addr[2] = p[3];
addr[3] = p[4];
addr[4] = ',';
addr[5] = p[6];
addr[6] = p[7];
addr[7] = ',';
addr[8] = p[9];
addr[9] = p[10];
addr[10] = p[12];
addr[11] = p[13];
addr[12] = p[15];
addr[13] = p[16];
addr[14] = '\0';
write_port_str(hPort, "AT+ROLE=1\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
write_port_str(hPort, "AT+CMODE=0\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "AT+BIND=%s\r\n", addr);
write_port_str(hPort, buffer);
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
msgtype = 4;
lstrcpy(szMessage, TEXT("已配置好设备连接请求。\n请在松开KEY键的情况下给蓝牙模块重新上电,模块将自动连接所选设备并通信。通信时请使用115200波特率。\n若所选设备为另一个HC-05蓝牙模块,那么这个蓝牙模块的配对密码必须要和当前蓝牙模块相同,才能连接成功。\n若所选设备为电脑,且蓝牙模块没有和此电脑配对,则蓝牙模块连接电脑时,电脑会自动提示配对新蓝牙设备并要求输入配对密码。\n\n请注意:HC-05蓝牙模块和电脑配对后,会产生两个COM口。\n一个是传入(Incoming),另一个是传出(Outgoing)。\n无论蓝牙模块是否在电脑附近,始终都能成功打开传入COM口。\n但只有当蓝牙模块在电脑附近时,才能成功打开传出COM口。\n由于这是蓝牙模块主动连接电脑,所以电脑上应该打开传入COM口。"));
break;
}
}
}
}
else
{
// 回到从机模式
write_port_str(hPort, "AT+ROLE=0\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") == 0)
{
msgtype = 4;
lstrcpy(szMessage, TEXT("已配置为从机模式。\n请在松开KEY键的情况下给蓝牙模块重新上电,然后在电脑上打开传出COM口,即可连接蓝牙模块。通信时请使用115200波特率。"));
break;
}
}
}
// 若连接不上, 则只有Connecting没有Connected, 设备还能继续响应AT指令
msgtype = 2;
lstrcpy(szMessage, TEXT("连接设备失败!"));
break;
case 3:
/* 重命名 */
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT("正在修改设备名称..."));
if (!ishc05)
{
p = gettext_utf8(hwndEdit[0]);
if (p != NULL)
{
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "AT+NAME%s\r\n", p);
free(p);
write_port_str(hPort, buffer);
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+NAME=", 6) == 0)
read_port_line(hPort, buffer, sizeof(buffer), 0);
}
if (p == NULL || strcmp(buffer, "OK") != 0)
{
lstrcpy(szMessage, TEXT("设备名称修改失败"));
break;
}
// 复位后生效
write_port_str(hPort, "AT+RESET\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "+RESET") == 0)
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") != 0)
{
msgtype = 3;
lstrcpy(szMessage, TEXT("设备名称修改成功,但需要重启模块后才能生效。"));
break;
}
}
else
{
p = gettext_utf8(hwndEdit[0]);
if (p != NULL)
{
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "AT+NAME=%s\r\n", p);
free(p);
write_port_str(hPort, buffer);
read_port_line(hPort, buffer, sizeof(buffer), 0);
}
if (p == NULL || strcmp(buffer, "OK") != 0)
{
lstrcpy(szMessage, TEXT("设备名称修改失败"));
break;
}
// HC-05不需要复位, 立即就能生效
}
lstrcpy(szMessage, TEXT("设备名称修改成功"));
break;
case 4:
/* 设置密码 */
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT("正在修改配对密码..."));
if (!ishc05)
{
p = gettext_utf8(hwndEdit[1]);
if (p != NULL)
{
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "AT+PIN%s\r\n", p);
free(p);
write_port_str(hPort, buffer);
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strncmp(buffer, "+PIN=", 5) == 0)
read_port_line(hPort, buffer, sizeof(buffer), 0);
}
if (p == NULL || strcmp(buffer, "OK") != 0)
{
lstrcpy(szMessage, TEXT("设备配对密码修改失败"));
break;
}
// 复位后生效
write_port_str(hPort, "AT+RESET\r\n");
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "+RESET") == 0)
read_port_line(hPort, buffer, sizeof(buffer), 0);
if (strcmp(buffer, "OK") != 0)
{
msgtype = 3;
lstrcpy(szMessage, TEXT("设备配对密码修改成功,但需要重启模块后才能生效。"));
break;
}
}
else
{
p = gettext_utf8(hwndEdit[1]);
if (p != NULL)
{
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "AT+PSWD=%s\r\n", p);
free(p);
write_port_str(hPort, buffer);
read_port_line(hPort, buffer, sizeof(buffer), 0);
}
if (p == NULL || strcmp(buffer, "OK") != 0)
{
lstrcpy(szMessage, TEXT("设备配对密码修改失败"));
break;
}
}
lstrcpy(szMessage, TEXT("设备配对密码修改成功"));
break;
}
end:
close_port(hPort);
PostMessage(hwndDlg, WM_USER, opcode, msgtype);
return 0;
}
static void copy_hc05_addr(char *dest, int destsize, const char *src)
{
int values[3] = {0};
sscanf_s(src, "%x:%x:%x", &values[0], &values[1], &values[2]);
_snprintf_s(dest, destsize, destsize, "%02X:%02X:%02X:%02X:%02X:%02X",
(values[0] >> 8) & 0xff, values[0] & 0xff,
values[1] & 0xff,
(values[2] >> 16) & 0xff, (values[2] >> 8) & 0xff, values[2] & 0xff
);
}
static void dlg_init(void)
{
int cx, cy;
HICON hIcon;
LVCOLUMN lvc;
SetDlgItemText(hwndDlg, IDC_STATIC2, TEXT(""));
SetDlgItemText(hwndDlg, IDC_STATIC4, TEXT(""));
hwndButton[0] = GetDlgItem(hwndDlg, IDOK);
hwndButton[1] = GetDlgItem(hwndDlg, IDC_BUTTON1);
hwndButton[2] = GetDlgItem(hwndDlg, IDC_BUTTON2);
hwndButton[3] = GetDlgItem(hwndDlg, IDC_BUTTON3);
hwndEdit[0] = GetDlgItem(hwndDlg, IDC_EDIT1);
hwndEdit[1] = GetDlgItem(hwndDlg, IDC_EDIT2);
hwndList = GetDlgItem(hwndDlg, IDC_LIST1);
ListView_SetExtendedListViewStyle(hwndList, LVS_EX_DOUBLEBUFFER);
// 启用Vista风格列表控件
// 请注意: 调用此函数设置主题成功后, 一定要响应ListView的NM_KILLFOCUS消息, 调用InvalidateRect重绘控件
// 不然窗口失去焦点后, 选择框文字部分是灰色的, 但图标部分仍然是蓝色的
SetWindowTheme(hwndList, L"Explorer", NULL);
cx = GetSystemMetrics(SM_CXSMICON);
cy = GetSystemMetrics(SM_CYSMICON);
hImageList = ImageList_Create(cx, cy, ILC_COLOR32, 1, 1);
hIcon = LoadImage(hinstMain, MAKEINTRESOURCE(IDI_ICON2), IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR);
ImageList_AddIcon(hImageList, hIcon);
ListView_SetImageList(hwndList, hImageList, LVSIL_SMALL);
lvc.mask = LVCF_TEXT | LVCF_WIDTH;
lvc.cx = 250;
lvc.pszText = TEXT("设备地址");
ListView_InsertColumn(hwndList, 0, &lvc);
lvc.cx = 100;
lvc.pszText = TEXT("信号强度");
ListView_InsertColumn(hwndList, 1, &lvc);
add_device(NULL);
}
static void enable_controls(BOOL enabled)
{
EnableWindow(hwndButton[0], enabled);
EnableWindow(hwndButton[1], enabled);
EnableWindow(hwndButton[2], enabled);
EnableWindow(hwndButton[3], (enabled && ListView_GetSelectedCount(hwndList) != 0));
Edit_SetReadOnly(hwndEdit[0], !enabled);
Edit_SetReadOnly(hwndEdit[1], !enabled);
}
static void start_bluetooth_process(uintptr_t opcode)
{
if (!thread_active)
{
thread_active = 1;
szMessage[0] = '\0';
enable_controls(FALSE);
if (opcode == 1)
{
ListView_DeleteAllItems(hwndList);
add_device(NULL);
}
hThread = CreateThread(NULL, 0, bluetooth_process, (LPVOID)opcode, 0, NULL);
}
}
INT_PTR CALLBACK Bluetooth_DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int wmId;
LPCTSTR title = NULL;
LPNMHDR lpnmhdr;
LPNMITEMACTIVATE lpnmia;
switch (uMsg)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
switch (wmId)
{
case IDOK:
start_bluetooth_process(1);
break;
case IDCANCEL:
if (thread_active)
{
// 请求停止线程里面的耗时操作
searching_devices = 0;
testing_baudrate = 0;
}
else
EndDialog(hDlg, 0);
break;
case IDC_BUTTON1:
if (Edit_GetTextLength(hwndEdit[0]) != 0)
start_bluetooth_process(3);
else
{
MessageBox(hDlg, TEXT("设备名称不能为空!"), TEXT("重命名设备"), MB_ICONWARNING);
SetFocus(hwndEdit[0]);
}
break;
case IDC_BUTTON2:
if (Edit_GetTextLength(hwndEdit[1]) != 0)
start_bluetooth_process(4);
else
{
MessageBox(hDlg, TEXT("设备配对密码不能为空!"), TEXT("设置密码"), MB_ICONWARNING);
SetFocus(hwndEdit[1]);
}
break;
case IDC_BUTTON3:
if (ListView_GetSelectedCount(hwndList) != 0)
start_bluetooth_process(2);
break;
}
break;
case WM_INITDIALOG:
hwndDlg = hDlg;
dlg_init();
break;
case WM_NOTIFY:
lpnmhdr = (LPNMHDR)lParam;
if (lpnmhdr->idFrom == IDC_LIST1)
{
switch (lpnmhdr->code)
{
case LVN_ITEMACTIVATE:
case NM_CLICK:
case NM_RCLICK:
lpnmia = (LPNMITEMACTIVATE)lpnmhdr;
if (lpnmia->iItem != -1 && !thread_active)
{
EnableWindow(hwndButton[3], TRUE);
// 若双击了项目, 则开始连接
if (lpnmhdr->code == LVN_ITEMACTIVATE)
start_bluetooth_process(2);
}
else
EnableWindow(hwndButton[3], FALSE);
break;
case LVN_KEYDOWN:
PostMessage(hDlg, WM_USER, 100, 0);
break;
case NM_KILLFOCUS:
InvalidateRect(lpnmhdr->hwndFrom, NULL, FALSE);
break;
}
}
break;
case WM_USER:
if (wParam >= 1 && wParam <= 4)
{
switch (wParam)
{
case 1:
title = TEXT("搜索设备");
break;
case 2:
title = TEXT("连接设备");
break;
case 3:
title = TEXT("重命名设备");
break;
case 4:
title = TEXT("设置密码");
break;
}
switch (lParam)
{
case 1:
// 改变提示文字
SetDlgItemText(hDlg, IDC_STATIC2, szMessage);
break;
case 2:
// 弹出警告窗口
MessageBox(hDlg, szMessage, title, MB_ICONWARNING);
SetDlgItemText(hDlg, IDC_STATIC2, TEXT(""));
break;
case 3:
// 弹出信息窗口
MessageBox(hDlg, szMessage, title, MB_ICONINFORMATION);
SetDlgItemText(hDlg, IDC_STATIC2, TEXT(""));
break;
case 4:
// 弹出信息窗口并关闭对话框
MessageBox(hDlg, szMessage, title, MB_ICONINFORMATION);
EndDialog(hDlg, 0);
break;
}
thread_active = 0;
enable_controls(TRUE);
}
else if (wParam == 100)
EnableWindow(hwndButton[3], ListView_GetSelectedCount(hwndList) != 0 && !thread_active);
break;
}
return 0;
}
encoding.c:
#include
#include "encoding.h"
/* 获取文本框输入的内容, 并转换成UTF8编码 */
char *gettext_utf8(HWND hWnd)
{
char *s = NULL;
int n;
wchar_t *ws;
n = GetWindowTextLengthW(hWnd) + 1;
ws = malloc(n * sizeof(wchar_t));
if (ws != NULL)
{
GetWindowTextW(hWnd, ws, n);
s = utf16to8(ws);
free(ws);
}
return s;
}
/* UTF8转UTF16 */
wchar_t *utf8to16(char *s)
{
wchar_t *ws;
int n;
n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
ws = malloc(n * sizeof(wchar_t));
if (ws != NULL)
MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, n);
return ws;
}
int utf8to16_wbuf(char *s, wchar_t *wbuf, int bufsize)
{
return MultiByteToWideChar(CP_UTF8, 0, s, -1, wbuf, bufsize / sizeof(wchar_t));
}
/* UTF16转UTF8 */
char *utf16to8(wchar_t *ws)
{
char *s;
int n;
n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
s = malloc(n);
if (s != NULL)
WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, n, NULL, NULL);
return s;
}