[STM32U3] 【STM32U385RG 测评】8、USB CDC+AES加密通讯

[复制链接]
 楼主| sujingliang 发表于 2025-7-23 16:03 | 显示全部楼层 |阅读模式
为实现USB接口与上位机加密通讯,将STM32U385RG配置为USB CDC设备,将ADC采集的明文数据经过AES ECB加密,密文通过CDC发送给PC机,PC机解密后显示ADC采集结果




一、USB CDC配置
STM32U385RG示例中有一个CDC的例程,可以直接用
\STM32Cube\Repository\STM32Cube_FW_U3_V1.2.0\Projects\NUCLEO-U385RG-Q\Applications\USBX\Ux_Device_CDC_ACM

先扩大一下pool  size,便于增加线程
40.png


1、app_usbx_device.c
在不破坏原有文件结构的基础上,增加一个THREDX线程来处理ADC采集、AES和CDC输出
UINT MX_USBX_Device_Init(VOID *memory_ptr)中增加my_cdc_thread定义
  1. if (tx_byte_allocate(byte_pool, (VOID **) &pointer, 1024, TX_NO_WAIT) != TX_SUCCESS)
  2.   {
  3.     return TX_POOL_ERROR;
  4.   }

  5.   /* Create the usbx_cdc_acm_write_thread_entry thread */
  6.   if (tx_thread_create(&my_cdc_thread, "my_cdc_thread_entry",
  7.                        my_cdc_thread_entry, 1, pointer,
  8.                        1024, 20, 20, TX_NO_TIME_SLICE,
  9.                        TX_AUTO_START) != TX_SUCCESS)
  10.   {
  11.     return TX_THREAD_ERROR;
  12.   }
2、ux_device_cdc_acm.c
my_cdc_thread_entry线程函数主要完成
ADC校准、在CDC就绪的情况启动ADC中断采集方式、通过tx_event_flags_get函数获得采集完成标志、去读ADC数据明文、通过encrypt_ADC_data加密、通过UART_SendU32_BigEndian()函数经USB CDC发送给PC机
  1. VOID my_cdc_thread_entry(ULONG thread_input)
  2. {
  3.         UX_SLAVE_DEVICE *device;
  4.         ULONG senddataflag = 0;
  5.   UX_PARAMETER_NOT_USED(thread_input);

  6.   device = &_ux_system_slave->ux_system_slave_device;

  7.         /* Perform ADC calibration */
  8.   if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK)
  9.   {
  10.     /* Calibration Error */
  11.     Error_Handler();
  12.   }

  13. while (1)
  14.   {
  15.     /* Check if device is configured */
  16.                 /* Wait until the requested flag TX_NEW_TRANSMITTED_DATA is received */

  17.     if ((device->ux_slave_device_state == UX_DEVICE_CONFIGURED) && (cdc_acm != UX_NULL))
  18.     {
  19.                        
  20.                         /* Start ADC group regular conversion */
  21.                         if (HAL_ADC_Start_IT(&hadc1) != HAL_OK)
  22.                         {
  23.                                 /* Error: ADC conversion start could not be performed */
  24.                                 Error_Handler();
  25.                         }
  26.                        

  27.                         if (tx_event_flags_get(&EventFlag, TX_ADC_CPLT_DATA, TX_OR_CLEAR,
  28.                                                                                                          &senddataflag, TX_WAIT_FOREVER) != TX_SUCCESS)
  29.                         {
  30.                                 Error_Handler();
  31.                         }
  32.                

  33.                         Plaintext[0]=uhADCxConvertedData;
  34.                         Plaintext[1]=uhADCxConvertedData_Voltage_mVolt;
  35.                         Plaintext[2]=uhADCxConvertedData;
  36.                         Plaintext[3]=uhADCxConvertedData_Voltage_mVolt;
  37.                        
  38.                         encrypt_ADC_data();
  39.                        
  40.                         UART_SendU32_BigEndian(EncryptedText, 4);

  41.                 }
  42.                
  43.                 tx_thread_sleep(MS_TO_TICK(1000));
  44.         }
  45. }


二、ADC配置
40.png

41.png
ADC回调处理:
获取ADC数据(0-4096),转换为mV(0-3300),通过tx_event_flags_set设置ADC完成标志
  1. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
  2. {
  3.   /* Retrieve ADC conversion data */
  4.   uhADCxConvertedData = HAL_ADC_GetValue(hadc);

  5.   /* Computation of ADC conversions raw data to physical values           */
  6.   /* using helper macro.                                                  */
  7.   uhADCxConvertedData_Voltage_mVolt = __LL_ADC_CALC_DATA_TO_VOLTAGE(VDDA_APPLI, uhADCxConvertedData, LL_ADC_RESOLUTION_12B);

  8.   /* Update status variable of ADC unitary conversion                     */

  9.   ubAdcGrpRegularUnitaryConvStatus = 1;
  10.        
  11.         if (tx_event_flags_set(&EventFlag, TX_ADC_CPLT_DATA, TX_OR) != TX_SUCCESS)
  12.   {
  13.     Error_Handler();
  14.   }
  15. }


三、AES配置

42.png
加密方式:ECB(电子密码本)
密钥:__ALIGN_BEGIN static const uint32_t pKeyAES[4] __ALIGN_END = {
                            0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10};

加密函数:
Plaintext是明文,EncryptedText是密文
  1. void encrypt_ADC_data()
  2. {
  3. if (HAL_CRYP_Encrypt(&hcryp, Plaintext, PLAINTEXT_SIZE, EncryptedText, TIMEOUT_VALUE) != HAL_OK)
  4.   {
  5.     /* Processing Error */
  6.     Error_Handler();
  7.   }
  8.        
  9. }
为了验证加解密是否正确可以访问https://tool.hiofd.com/aes-encrypt-online/进行在线的加解密,结果和HAL_CRYP_Encrypt对照。

为了CDC输出格式满足要求,编写UART_SendU32_BigEndian函数,负责格式转换和CDC输出
  1. void UART_SendU32_BigEndian( uint32_t *data, uint16_t word_count) {
  2.                 ULONG actual_length;
  3.     for (uint16_t i = 0; i < word_count; i++) {
  4.         uint8_t bytes[4];
  5.         bytes[0] = (data[i] >> 24) & 0xFF;  // 最高字节
  6.         bytes[1] = (data[i] >> 16) & 0xFF;
  7.         bytes[2] = (data[i] >> 8)  & 0xFF;
  8.         bytes[3] = data[i] & 0xFF;          // 最低字节
  9.       
  10.                                  
  11.                                 ux_device_class_cdc_acm_write(cdc_acm, (UCHAR *)&bytes,4, &actual_length);
  12.     }
  13. }


四、用PYTHON编写一个PC机处理函数

主要是处理串口接收,然后解密打印到终端
  1. import serial
  2. from Cryptodome.Cipher import AES
  3. from Cryptodome.Util.Padding import pad, unpad
  4. import struct

  5. # AES 密钥(必须16/24/32字节)
  6. AES_KEY = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10'  # AES-128 (16字节)

  7. def setup_serial(port, baudrate=115200):
  8.     """初始化串口"""
  9.     try:
  10.         ser = serial.Serial(port, baudrate, timeout=1)
  11.         print(f"串口 {port} 已打开,波特率 {baudrate}")
  12.         return ser
  13.     except Exception as e:
  14.         print(f"无法打开串口 {port}: {e}")
  15.         return None

  16. def decrypt_aes_ecb(ciphertext, key):
  17.     """AES-ECB 解密"""
  18.     cipher = AES.new(key, AES.MODE_ECB)
  19.     decrypted = cipher.decrypt(ciphertext)
  20.     return decrypted
  21.     #return unpad(decrypted, AES.block_size)  # 自动去除填充

  22. def main():
  23.     ser = setup_serial('COM45', 115200)  # 修改为你的串口号(如 /dev/ttyUSB0)
  24.     if not ser:
  25.         return

  26.     try:
  27.         while True:
  28.             if ser.in_waiting >= 16:  # 至少16字节(AES块大小)
  29.                 ciphertext = ser.read(16)  # 读取16字节加密数据
  30.                 print(f"\r\n接收加密数据: {ciphertext.hex()}")

  31.                 # AES-ECB 解密
  32.                 try:
  33.                     plaintext = decrypt_aes_ecb(ciphertext, AES_KEY)
  34.                     print("解密数据 (HEX):", plaintext.hex())  # 输出 HEX 格式
  35.                     print("解密数据 (Bytes):", plaintext)      # 直接输出字节
  36.                     uint32_value = struct.unpack('>L', plaintext[4:8])[0]  # '<L' 表示小端uint32
  37.                     print("转换为uint32:", uint32_value,"mA", hex(uint32_value))
  38.                 except Exception as e:
  39.                     print(f"解密失败: {e}")

  40.     except KeyboardInterrupt:
  41.         print("\n程序终止")
  42.     finally:
  43.         ser.close()

  44. if __name__ == "__main__":
  45.     main()


五、运行
Video_250723151648-ezgif.com-video-to-gif-converter.gif

发送了4个uint32_t,第一、三是ADC的采集数据(0-4096),第二、四是电压mV(上面笔误写的mA)


捉虫天师 发表于 2025-7-25 12:23 | 显示全部楼层
PY也可以串口通信啊,666?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

84

主题

146

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部