Bootstrap

STM32端与C#端的连接与交互[外设][代码记录]

1. 通过UART串口进行通信

STM32 端代码(UART通信):
#include "stm32f1xx_hal.h"

UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    char message[] = "Hello from STM32!\r\n";

    while (1)
    {
        HAL_UART_Transmit(&huart1, (uint8_t*)message, sizeof(message), HAL_MAX_DELAY);
        HAL_Delay(1000);  // 发送间隔1秒
    }
}

static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 9600;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    HAL_UART_Init(&huart1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(通过串口读取STM32的数据):
using System;
using System.IO.Ports;

class Program
{
    static void Main()
    {
        SerialPort port = new SerialPort("COM3", 9600); // 使用串口3, 波特率9600
        port.Open();
        port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

        Console.WriteLine("等待数据...");
        Console.ReadLine(); // 保持程序运行
        port.Close();
    }

    private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort sp = (SerialPort)sender;
        string inData = sp.ReadExisting(); // 读取接收到的数据
        Console.WriteLine($"接收到的数据: {inData}");
    }
}

2. 通过USB-CDC进行通信

STM32 端代码(配置为USB CDC虚拟串口):
  1. 使用STM32CubeMX生成USB CDC代码。
  2. usbd_cdc_if.c 文件中修改发送数据的部分:
#include "usbd_cdc_if.h"

uint8_t usb_data[] = "Hello from STM32 via USB!\r\n";

void send_usb_data(void)
{
    CDC_Transmit_FS(usb_data, sizeof(usb_data)); // 通过USB发送数据
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_USB_DEVICE_Init(); // 初始化USB设备

    while (1)
    {
        send_usb_data();
        HAL_Delay(1000);  // 每秒发送一次
    }
}
C# 端代码(通过虚拟串口与STM32通信):
using System;
using System.IO.Ports;

class Program
{
    static void Main()
    {
        SerialPort usbPort = new SerialPort("COM4", 115200); // 设置虚拟串口COM4
        usbPort.Open();
        usbPort.DataReceived += new SerialDataReceivedEventHandler(USBDataReceivedHandler);

        Console.WriteLine("USB 连接已打开...");
        Console.ReadLine(); // 保持程序运行
        usbPort.Close();
    }

    private static void USBDataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort sp = (SerialPort)sender;
        string inData = sp.ReadExisting();
        Console.WriteLine($"通过USB接收到的数据: {inData}");
    }
}

3. 通过I²C通信(使用FTDI USB-I²C适配器)

STM32 端代码(配置为I²C主机):
#include "stm32f1xx_hal.h"

I2C_HandleTypeDef hi2c1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_I2C1_Init();

    uint8_t data[] = {0x01, 0x02, 0x03}; // 要发送的I2C数据
    HAL_I2C_Master_Transmit(&hi2c1, 0x68 << 1, data, sizeof(data), HAL_MAX_DELAY); // 发送数据到I2C从设备地址0x68

    while (1) {}
}

static void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    HAL_I2C_Init(&hi2c1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(使用USB-I²C适配器读取STM32发送的数据):

C# 端需要使用FTDI提供的库,例如 D2XX 驱动库。

// 假设使用 FTDI I2C 适配器,调用 FTDI 的 D2XX 库

4. 通过以太网进行TCP通信

STM32 端代码(配置为TCP服务器):
  1. 使用 STM32CubeMX 生成 LWIP 网络栈,配置以太网和 TCP 服务器。
  2. 在主程序中实现TCP通信。
#include "lwip/opt.h"
#include "lwip/tcp.h"
#include "lwip.h"
#include <string.h>

void tcp_server_init(void);

err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    if (p != NULL)
    {
        tcp_recved(tpcb, p->tot_len);
        tcp_write(tpcb, "Hello from STM32 TCP Server!", strlen("Hello from STM32 TCP Server!"), 1);
        pbuf_free(p);
    }
    return ERR_OK;
}

void tcp_server_init(void)
{
    struct tcp_pcb *pcb;
    pcb = tcp_new();
    tcp_bind(pcb, IP_ADDR_ANY, 80); // 绑定到端口80
    pcb = tcp_listen(pcb);
    tcp_accept(pcb, recv_callback);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_LWIP_Init(); // 初始化以太网栈

    tcp_server_init(); // 启动TCP服务器

    while (1)
    {
        MX_LWIP_Process(); // 处理以太网数据
    }
}
C# 端代码(作为TCP客户端连接到STM32):
using System;
using System.Net.Sockets;
using System.Text;

class Program
{
    static void Main()
    {
        TcpClient client = new TcpClient("192.168.0.10", 80); // STM32的IP地址和端口
        NetworkStream stream = client.GetStream();

        byte[] data = Encoding.ASCII.GetBytes("Hello STM32 via Ethernet");
        stream.Write(data, 0, data.Length);

        byte[] response = new byte[256];
        int bytes = stream.Read(response, 0, response.Length);
        Console.WriteLine($"收到的回复: {Encoding.ASCII.GetString(response, 0, bytes)}");

        stream.Close();
        client.Close();
    }
}

当然,下面是关于通过CAN总线通信(使用USB-CAN适配器)的完整示例,包括STM32端和C#端的代码。

5. 通过CAN总线通信(使用USB-CAN适配器)

STM32 端代码(配置为CAN节点):
#include "stm32f1xx_hal.h"

CAN_HandleTypeDef hcan1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CAN1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_CAN1_Init();

    CAN_TxHeaderTypeDef TxHeader;
    uint32_t TxMailbox;
    uint8_t TxData[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

    TxHeader.StdId = 0x321;  // 标准ID
    TxHeader.ExtId = 0x01;   // 扩展ID(如果需要)
    TxHeader.RTR = CAN_RTR_DATA;  // 数据帧
    TxHeader.IDE = CAN_ID_STD;     // 标准ID
    TxHeader.DLC = 8;               // 数据长度

    while (1)
    {
        HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox); // 发送CAN数据
        HAL_Delay(1000); // 每秒发送一次
    }
}

static void MX_CAN1_Init(void)
{
    hcan1.Instance = CAN1;
    hcan1.Init.Prescaler = 16; // 设置分频器
    hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常模式
    hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳宽
    hcan1.Init.TimeSeg1 = CAN_BS1_8TQ; // 时间段1
    hcan1.Init.TimeSeg2 = CAN_BS2_3TQ; // 时间段2
    hcan1.Init.AutoBusOff = DISABLE; // 自动总线关闭
    hcan1.Init.AutoWakeUp = DISABLE; // 自动唤醒
    hcan1.Init.ReceiveFifoLocked = DISABLE; // 接收FIFO锁定
    hcan1.Init.TransmitFifoPriority = DISABLE; // 发送FIFO优先级
    HAL_CAN_Init(&hcan1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}

C# 端代码(通过USB-CAN适配器读取CAN数据):

using System;
using System.IO.Ports;

class Program
{
    static void Main()
    {
        // 假设使用特定厂商的CAN库与USB-CAN适配器通信
        SerialPort canPort = new SerialPort("COM5", 115200); // 根据实际连接的COM端口
        canPort.Open();

        while (true)
        {
            string message = canPort.ReadLine(); // 假设CAN数据以行分隔
            Console.WriteLine($"接收到的CAN数据: {message}");
        }

        canPort.Close();
    }
}

总结

  1. STM32代码

    • STM32配置为CAN节点,周期性地发送数据帧。
    • 使用HAL_CAN_AddTxMessage函数发送CAN数据。
  2. C#代码

    • C#端通过USB-CAN适配器接收CAN数据,假设使用SerialPort进行串口通信。
    • 可以根据实际需要将COM5替换为相应的串口号。

注意事项:

  • 确保STM32的CAN总线连接正确,并且终端电阻已正确配置。
  • C#端可能需要特定的CAN库,具体取决于所使用的USB-CAN适配器。
  • 根据实际应用,可能需要对发送的数据格式进行调整。

好的,以下是更多关于STM32外设与C#的实战示例,继续展示不同的外设连接和通信方式:

6. 通过SPI通信

STM32 端代码(配置为SPI主机):
#include "stm32f1xx_hal.h"

SPI_HandleTypeDef hspi1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_SPI1_Init();

    uint8_t dataToSend[3] = {0xAA, 0xBB, 0xCC}; // 要发送的数据
    uint8_t receivedData[3];

    while (1)
    {
        HAL_SPI_TransmitReceive(&hspi1, dataToSend, receivedData, sizeof(dataToSend), HAL_MAX_DELAY);
        HAL_Delay(1000); // 每秒发送一次
    }
}

static void MX_SPI1_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    HAL_SPI_Init(&hspi1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(使用SPI与STM32通信):
using System;
using System.Device.Spi;
using System.Device.Spi.Drivers;

class Program
{
    static void Main()
    {
        // 假设使用 SPI 设备, SPI 0 为主设备
        using (SpiDevice spiDevice = new SpiDevice(new SpiConnectionSettings(0, 0)))
        {
            byte[] dataToSend = { 0xAA, 0xBB, 0xCC }; // 要发送的数据
            byte[] receivedData = new byte[dataToSend.Length];

            while (true)
            {
                spiDevice.Write(dataToSend); // 发送数据
                spiDevice.Read(receivedData); // 读取返回的数据
                Console.WriteLine($"接收到的数据: {BitConverter.ToString(receivedData)}");
                System.Threading.Thread.Sleep(1000); // 每秒发送一次
            }
        }
    }
}

7. 通过PWM控制LED

STM32 端代码(配置为PWM输出):
#include "stm32f1xx_hal.h"

TIM_HandleTypeDef htim2;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();

    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 启动PWM

    while (1)
    {
        for (uint32_t pulse = 0; pulse < 1000; pulse += 10)
        {
            __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse); // 设置PWM占空比
            HAL_Delay(100); // 延时
        }
    }
}

static void MX_TIM2_Init(void)
{
    TIM_OC_InitTypeDef sConfigOC = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1; // 设置时钟
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 1000 - 1; // 设置PWM周期
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim2);

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0; // 初始占空比
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(控制PWM的占空比):
using System;
using System.IO.Ports;

class Program
{
    static void Main()
    {
        SerialPort port = new SerialPort("COM3", 9600);
        port.Open();

        while (true)
        {
            for (int pulse = 0; pulse <= 1000; pulse += 10)
            {
                port.WriteLine(pulse.ToString()); // 发送PWM占空比
                System.Threading.Thread.Sleep(100); // 延时
            }
        }
        
        port.Close();
    }
}

8. 通过ADC读取模拟传感器数据

STM32 端代码(配置为ADC读取):
#include "stm32f1xx_hal.h"

ADC_HandleTypeDef hadc1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    uint32_t adcValue;

    while (1)
    {
        HAL_ADC_Start(&hadc1);
        HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
        adcValue = HAL_ADC_GetValue(&hadc1); // 读取ADC值
        HAL_ADC_Stop(&hadc1);
        HAL_Delay(1000); // 每秒读取一次
    }
}

static void MX_ADC1_Init(void)
{
    hadc1.Instance = ADC1;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    HAL_ADC_Init(&hadc1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(读取ADC数据并处理):
using System;
using System.IO.Ports;

class Program
{
    static void Main()
    {
        SerialPort port = new SerialPort("COM3", 9600);
        port.Open();

        while (true)
        {
            string adcValue = port.ReadLine(); // 读取ADC值
            Console.WriteLine($"ADC值: {adcValue}");
        }

        port.Close();
    }
}

9. 通过RTC获取实时时钟

STM32 端代码(配置RTC):
#include "stm32f1xx_hal.h"

RTC_HandleTypeDef hrtc;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_RTC_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_RTC_Init();

    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;

    while (1)
    {
        HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
        HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
        HAL_Delay(1000); // 每秒获取一次时间
    }
}

static void MX_RTC_Init(void)
{
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;
    hrtc.Init.SynchPrediv = 255;
    HAL_RTC_Init(&hrtc);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(获取RTC数据):
using System;
using System.IO.Ports;

class Program
{
    static void Main()
    {
        SerialPort port = new SerialPort("COM3", 9600);
        port.Open();

        while (true)
        {
            string timeData = port.ReadLine(); // 读取RTC数据
            Console.WriteLine($"当前时间: {timeData}");
        }

        port.Close();
    }
}

10. 通过SD卡进行文件读写

STM32 端代码(使用FATFS读写SD卡):
#include "ff.h" // FATFS头文件
#include "stm32f1xx_hal.h"

FATFS FatFs;   // FatFs工作区
FIL fil;       // 文件对象
char buffer[100];

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    f_mount(&FatFs, "", 1); //

 挂载文件系统
    f_open(&fil, "test.txt", FA_WRITE | FA_CREATE_ALWAYS); // 打开文件
    f_write(&fil, "Hello, STM32!", sizeof("Hello, STM32!"), NULL); // 写入数据
    f_close(&fil); // 关闭文件

    while (1);
}

static void SystemClock_Config(void)
{
    // 时钟配置代码
}

static void MX_GPIO_Init(void)
{
    // GPIO初始化代码
}
C# 端代码(读取SD卡文件):
using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = @"\\path_to_sd_card\\test.txt"; // SD卡路径
        while (true)
        {
            if (File.Exists(path))
            {
                string content = File.ReadAllText(path); // 读取文件内容
                Console.WriteLine($"文件内容: {content}");
            }
            System.Threading.Thread.Sleep(1000); // 每秒检查一次
        }
    }
}

总结

这些示例展示了如何使用STM32微控制器的多种外设进行实际应用,同时与C#进行数据通信。可以根据实际需求,选择合适的外设和通信方式进行开发。作为一版笔记记录下来!

;