Skip to content

嵌入式驱动开发实践

RS-485 驱动(STM32)

c
// RS-485 半双工驱动实现
// 使用 DE/RE 引脚控制收发方向

#include "stm32h7xx_hal.h"

#define RS485_DE_PIN    GPIO_PIN_12
#define RS485_DE_PORT   GPIOA

// 方向控制宏
#define RS485_TX_MODE() HAL_GPIO_WritePin(RS485_DE_PORT, RS485_DE_PIN, GPIO_PIN_SET)
#define RS485_RX_MODE() HAL_GPIO_WritePin(RS485_DE_PORT, RS485_DE_PIN, GPIO_PIN_RESET)

// DMA 发送缓冲区
static uint8_t tx_buffer[256];
static uint8_t rx_buffer[256];
static volatile uint8_t tx_complete = 1;

// 初始化
void RS485_Init(void) {
    // 配置 DE 引脚为输出
    GPIO_InitTypeDef gpio = {
        .Pin = RS485_DE_PIN,
        .Mode = GPIO_MODE_OUTPUT_PP,
        .Pull = GPIO_NOPULL,
        .Speed = GPIO_SPEED_FREQ_HIGH
    };
    HAL_GPIO_Init(RS485_DE_PORT, &gpio);
    RS485_RX_MODE();  // 默认接收模式

    // 配置 UART(9600, 8N1)
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 9600;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    HAL_UART_Init(&huart2);

    // 启动 DMA 接收
    HAL_UART_Receive_DMA(&huart2, rx_buffer, sizeof(rx_buffer));
}

// 发送数据(带方向控制)
HAL_StatusTypeDef RS485_Send(const uint8_t *data, uint16_t len) {
    if (!tx_complete) return HAL_BUSY;

    memcpy(tx_buffer, data, len);
    tx_complete = 0;

    RS485_TX_MODE();  // 切换到发送模式
    // 小延迟确保方向切换稳定(根据硬件调整)
    __NOP(); __NOP(); __NOP();

    return HAL_UART_Transmit_DMA(&huart2, tx_buffer, len);
}

// 发送完成回调(DMA 中断)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART2) {
        // 等待最后一个字节发送完成(TC 标志)
        while (__HAL_UART_GET_FLAG(huart, UART_FLAG_TC) == RESET);
        RS485_RX_MODE();  // 切换回接收模式
        tx_complete = 1;
    }
}

CAN 总线驱动

c
// STM32 CAN 驱动(用于 BMS 通信)
#include "stm32h7xx_hal.h"

FDCAN_HandleTypeDef hfdcan1;

void CAN_Init(void) {
    hfdcan1.Instance = FDCAN1;
    hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
    hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;  // 经典 CAN 2.0
    hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
    hfdcan1.Init.AutoRetransmission = ENABLE;
    hfdcan1.Init.TransmitPause = DISABLE;
    hfdcan1.Init.ProtocolException = ENABLE;

    // 波特率:250kbps(时钟 80MHz)
    hfdcan1.Init.NominalPrescaler = 16;
    hfdcan1.Init.NominalSyncJumpWidth = 1;
    hfdcan1.Init.NominalTimeSeg1 = 13;
    hfdcan1.Init.NominalTimeSeg2 = 6;

    HAL_FDCAN_Init(&hfdcan1);

    // 配置接收过滤器(接收所有扩展帧)
    FDCAN_FilterTypeDef filter = {
        .IdType = FDCAN_EXTENDED_ID,
        .FilterIndex = 0,
        .FilterType = FDCAN_FILTER_MASK,
        .FilterConfig = FDCAN_FILTER_TO_RXFIFO0,
        .FilterID1 = 0x00000000,
        .FilterID2 = 0x00000000  // 掩码全0=接收所有
    };
    HAL_FDCAN_ConfigFilter(&hfdcan1, &filter);
    HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,
        FDCAN_REJECT, FDCAN_REJECT,
        FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE);

    // 启动 CAN
    HAL_FDCAN_Start(&hfdcan1);
    HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
}

// 发送 CAN 帧
HAL_StatusTypeDef CAN_Send(uint32_t can_id, const uint8_t *data, uint8_t len) {
    FDCAN_TxHeaderTypeDef tx_header = {
        .Identifier = can_id,
        .IdType = FDCAN_EXTENDED_ID,
        .TxFrameType = FDCAN_DATA_FRAME,
        .DataLength = len,
        .ErrorStateIndicator = FDCAN_ESI_ACTIVE,
        .BitRateSwitch = FDCAN_BRS_OFF,
        .FDFormat = FDCAN_CLASSIC_CAN,
        .TxEventFifoControl = FDCAN_NO_TX_EVENTS,
        .MessageMarker = 0
    };
    return HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &tx_header, data);
}

// 接收中断回调
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) {
    FDCAN_RxHeaderTypeDef rx_header;
    uint8_t rx_data[8];

    while (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0,
                                   &rx_header, rx_data) == HAL_OK) {
        // 处理接收到的 CAN 帧
        CAN_ProcessMessage(rx_header.Identifier, rx_data, rx_header.DataLength);
    }
}

SPI Flash 驱动

c
// W25Q128 SPI Flash 驱动(用于数据缓存)
#define W25Q_PAGE_SIZE      256
#define W25Q_SECTOR_SIZE    4096
#define W25Q_TOTAL_SIZE     (16 * 1024 * 1024)  // 16MB

// 命令定义
#define CMD_WRITE_ENABLE    0x06
#define CMD_READ_STATUS1    0x05
#define CMD_PAGE_PROGRAM    0x02
#define CMD_SECTOR_ERASE    0x20
#define CMD_READ_DATA       0x03
#define CMD_CHIP_ERASE      0xC7

static void SPI_CS_Low(void)  { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); }
static void SPI_CS_High(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); }

// 等待 Flash 就绪
static void W25Q_WaitBusy(void) {
    uint8_t cmd = CMD_READ_STATUS1;
    uint8_t status;
    do {
        SPI_CS_Low();
        HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
        HAL_SPI_Receive(&hspi1, &status, 1, 100);
        SPI_CS_High();
    } while (status & 0x01);  // WIP 位
}

// 写使能
static void W25Q_WriteEnable(void) {
    uint8_t cmd = CMD_WRITE_ENABLE;
    SPI_CS_Low();
    HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
    SPI_CS_High();
}

// 扇区擦除(4KB)
void W25Q_EraseSector(uint32_t addr) {
    W25Q_WriteEnable();
    uint8_t cmd[4] = {
        CMD_SECTOR_ERASE,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    SPI_CS_Low();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    SPI_CS_High();
    W25Q_WaitBusy();
}

// 页写入(最多 256 字节)
void W25Q_WritePage(uint32_t addr, const uint8_t *data, uint16_t len) {
    W25Q_WriteEnable();
    uint8_t cmd[4] = {
        CMD_PAGE_PROGRAM,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    SPI_CS_Low();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Transmit(&hspi1, (uint8_t*)data, len, 1000);
    SPI_CS_High();
    W25Q_WaitBusy();
}

// 读取数据
void W25Q_Read(uint32_t addr, uint8_t *buf, uint32_t len) {
    uint8_t cmd[4] = {
        CMD_READ_DATA,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    SPI_CS_Low();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Receive(&hspi1, buf, len, 5000);
    SPI_CS_High();
}

低功耗设计

c
// STM32 低功耗模式管理

typedef enum {
    POWER_MODE_RUN,     // 正常运行
    POWER_MODE_SLEEP,   // 睡眠(CPU 停止,外设运行)
    POWER_MODE_STOP2,   // 停止2(最低功耗,保留 RAM)
    POWER_MODE_STANDBY  // 待机(最低功耗,RAM 丢失)
} PowerMode_t;

// 进入停止模式(等待中断唤醒)
void EnterStopMode(void) {
    // 确保所有外设操作完成
    HAL_UART_DeInit(&huart2);

    // 配置唤醒源(RTC 闹钟)
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 30, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);

    // 进入停止模式
    HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);

    // 唤醒后重新初始化时钟
    SystemClock_Config();
    HAL_UART_Init(&huart2);
}

// 动态电压频率调节(DVFS)
void SetLowPerformanceMode(void) {
    // 降低 CPU 频率(从 480MHz 降到 120MHz)
    RCC_ClkInitTypeDef clk = {
        .ClockType = RCC_CLOCKTYPE_SYSCLK,
        .SYSCLKSource = RCC_SYSCLKSOURCE_HSI,
        .AHBCLKDivider = RCC_SYSCLK_DIV1,
        .APB1CLKDivider = RCC_HCLK_DIV2,
        .APB2CLKDivider = RCC_HCLK_DIV2
    };
    HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2);

    // 降低电压(VOS3 模式)
    HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE3);
}

Linux 内核驱动(嵌入式 Linux)

c
// 简单字符设备驱动示例(RS485 控制)
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/of.h>

struct rs485_dev {
    struct device *dev;
    int de_gpio;
    int re_gpio;
};

static int rs485_probe(struct platform_device *pdev) {
    struct rs485_dev *priv;
    struct device_node *np = pdev->dev.of_node;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv) return -ENOMEM;

    // 从设备树获取 GPIO
    priv->de_gpio = of_get_named_gpio(np, "de-gpios", 0);
    if (!gpio_is_valid(priv->de_gpio)) {
        dev_err(&pdev->dev, "Invalid DE GPIO\n");
        return -EINVAL;
    }

    // 申请 GPIO
    devm_gpio_request_one(&pdev->dev, priv->de_gpio,
                          GPIOF_OUT_INIT_LOW, "rs485-de");

    platform_set_drvdata(pdev, priv);
    dev_info(&pdev->dev, "RS485 driver probed\n");
    return 0;
}

static const struct of_device_id rs485_of_match[] = {
    { .compatible = "mycompany,rs485-ctrl" },
    {}
};
MODULE_DEVICE_TABLE(of, rs485_of_match);

static struct platform_driver rs485_driver = {
    .probe = rs485_probe,
    .driver = {
        .name = "rs485-ctrl",
        .of_match_table = rs485_of_match,
    },
};
module_platform_driver(rs485_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("RS485 Direction Control Driver");
c
// 设备树节点(DTS)
rs485_ctrl: rs485-ctrl {
    compatible = "mycompany,rs485-ctrl";
    de-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
    re-gpios = <&gpio1 13 GPIO_ACTIVE_LOW>;
    status = "okay";
};

褚成志的IoT笔记