打印
[STM32U3]

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

[复制链接]
455|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
为实现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,便于增加线程



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

  /* Create the usbx_cdc_acm_write_thread_entry thread */
  if (tx_thread_create(&my_cdc_thread, "my_cdc_thread_entry",
                       my_cdc_thread_entry, 1, pointer,
                       1024, 20, 20, TX_NO_TIME_SLICE,
                       TX_AUTO_START) != TX_SUCCESS)
  {
    return TX_THREAD_ERROR;
  }
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机
VOID my_cdc_thread_entry(ULONG thread_input)
{
        UX_SLAVE_DEVICE *device;
        ULONG senddataflag = 0;
  UX_PARAMETER_NOT_USED(thread_input);

  device = &_ux_system_slave->ux_system_slave_device;

        /* Perform ADC calibration */
  if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK)
  {
    /* Calibration Error */
    Error_Handler();
  }

while (1)
  {
    /* Check if device is configured */
                /* Wait until the requested flag TX_NEW_TRANSMITTED_DATA is received */

    if ((device->ux_slave_device_state == UX_DEVICE_CONFIGURED) && (cdc_acm != UX_NULL))
    {
                       
                        /* Start ADC group regular conversion */
                        if (HAL_ADC_Start_IT(&hadc1) != HAL_OK)
                        {
                                /* Error: ADC conversion start could not be performed */
                                Error_Handler();
                        }
                       

                        if (tx_event_flags_get(&EventFlag, TX_ADC_CPLT_DATA, TX_OR_CLEAR,
                                                                                                         &senddataflag, TX_WAIT_FOREVER) != TX_SUCCESS)
                        {
                                Error_Handler();
                        }
               

                        Plaintext[0]=uhADCxConvertedData;
                        Plaintext[1]=uhADCxConvertedData_Voltage_mVolt;
                        Plaintext[2]=uhADCxConvertedData;
                        Plaintext[3]=uhADCxConvertedData_Voltage_mVolt;
                       
                        encrypt_ADC_data();
                       
                        UART_SendU32_BigEndian(EncryptedText, 4);

                }
               
                tx_thread_sleep(MS_TO_TICK(1000));
        }
}


二、ADC配置



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

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

  /* Update status variable of ADC unitary conversion                     */

  ubAdcGrpRegularUnitaryConvStatus = 1;
       
        if (tx_event_flags_set(&EventFlag, TX_ADC_CPLT_DATA, TX_OR) != TX_SUCCESS)
  {
    Error_Handler();
  }
}


三、AES配置


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

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

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


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

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

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

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

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

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

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

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

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

if __name__ == "__main__":
    main()


五、运行


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


使用特权

评论回复
沙发
捉虫天师| | 2025-7-25 12:23 | 只看该作者
PY也可以串口通信啊,666?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

84

主题

147

帖子

3

粉丝