游乐游手机版
首页/AI教程/文章详情

STC8十六通道ADC四路串口八路PWM程序

时间:2026-07-09 15:45
基于STC8微控制器设计的多功能数据采集系统,采用分层架构,实现16路ADC模拟采集、4路UART串口通信和8路PWM输出。系统初始化后,以1kHzADC采样率采集数据,PWM基准频率1kHz、分辨率1000级,串口缓冲区128字节,支持看门狗和低功耗模式。

一、STC8 多功能数据采集系统架构设计

下面这张系统架构图清晰展示了 STC8 多功能数据采集系统的整体设计脉络。从底层向上看,模拟采集层负责处理 16 路 ADC 信号,通信接口层集成了四个 UART 串口,控制输出层提供 8 路 PWM 通道,最上层是系统管理层,涵盖初始化、中断管理、看门狗和低功耗模式。这种分层架构的优势在于——每层职责明确,代码维护直观便捷,后续扩展新功能只需在对应层添加模块即可。

┌─────────────────────────────────────────────────────────────┐
│                        STC8 多功能数据采集系统               │
├─────────────────────────────────────────────────────────────┤
│   模拟采集层      │   通信接口层    │   控制输出层    │   系统管理层   │
│                   │                │                │                │
│ • 16路ADC        │ • UART1       │ • 8路PWM       │ • 系统初始化   │
│ • 采样率配置      │ • UART2       │ • 频率调节     │ • 中断管理     │
│ • 滤波处理        │ • UART3       │ • 占空比控制   │ • 看门狗       │
│ • 数据校准        │ • UART4       │ • 死区设置     │ • 低功耗模式   │
└─────────────────────────────────────────────────────────────┘

二、硬件配置详解

2.1 STC8A8K64S4A12 引脚分配

硬件引脚分配是项目落地的关键环节,以下将关键引脚与功能完整映射列出。P0 和 P1 全部配置为高阻输入,专用于 16 路 ADC 模拟信号采集;P2 设为推挽输出,驱动 8 路 PWM;P3 同时承担四个串口的收发功能,注意复用逻辑——RXD1~RXD4 分别占用 P3.0、P3.2、P3.4、P3.6,TXD 对应相邻引脚。P4 上集成了参考电压控制、时钟输出、外部中断和复位引脚,属于系统级控制信号。

STC8A8K64S4A12 引脚配置:
┌─────────────┬─────────────────────┬──────────────────┐
│ 引脚         │ 功能描述            │ 说明              │
├─────────────┼─────────────────────┼──────────────────┤
│ P0.0-P0.7   │ ADC0-ADC7           │ 模拟输入通道0-7   │
│ P1.0-P1.7   │ ADC8-ADC15          │ 模拟输入通道8-15  │
│ P2.0-P2.7   │ PWM0-PWM7           │ PWM输出通道0-7    │
│ P3.0/RXD1   │ UART1接收           │ 串口1接收         │
│ P3.1/TXD1   │ UART1发送           │ 串口1发送         │
│ P3.2/RXD2   │ UART2接收           │ 串口2接收         │
│ P3.3/TXD2   │ UART2发送           │ 串口2发送         │
│ P3.4/RXD3   │ UART3接收           │ 串口3接收         │
│ P3.5/TXD3   │ UART3发送           │ 串口3发送         │
│ P3.6/RXD4   │ UART4接收           │ 串口4接收         │
│ P3.7/TXD4   │ UART4发送           │ 串口4发送         │
│ P4.0        │ ADC参考电压选择      │ 参考电压控制      │
│ P4.1        │ 系统时钟输出         │ 时钟输出          │
│ P4.2        │ 外部中断0            │ 外部触发          │
│ P4.3        │ 外部中断1            │ 外部触发          │
│ P5.0        │ 系统复位/唤醒        │ 复位引脚          │
└─────────────┴─────────────────────┴──────────────────┘

2.2 硬件连接示例

以下给出一组典型的接线方案,可直接参考:模拟输入侧,P0.0~P0.7 接第一组传感器,P1.0~P1.7 接第二组,参考电压从 P4.0 接入 2.5V 精密基准源。PWM 输出方面,P2.0~P2.3 可用于驱动电机或 LED 调光,P2.4~P2.7 接舵机或继电器。四个串口分别连接上位机、GPS 模块、无线通信模块和传感器扩展板,分工明确,单芯片即可完成多路数据交互。

模拟输入:
• 通道0-7:P0.0-P0.7,连接传感器信号
• 通道8-15:P1.0-P1.7,连接传感器信号
• 参考电压:P4.0,连接2.5V精密参考电压

PWM输出:
• PWM0-PWM3:P2.0-P2.3,连接电机驱动/LED调光
• PWM4-PWM7:P2.4-P2.7,连接舵机/继电器

串口通信:
• UART1:连接上位机/调试终端
• UART2:连接GPS模块
• UART3:连接无线通信模块
• UART4:连接传感器扩展板

三、完整源码实现

3.1 系统头文件 (stc8_multifunc.h)

头文件是整个工程的“地图”,所有宏定义、数据结构、函数声明都集中管理。可以看到系统时钟固定为 24MHz,ADC 通道数 16,每个通道采样 10 次取平均,PWM 基准频率 1kHz、分辨率 1000 级,串口缓冲区 128 字节。在数据结构方面,ADC_Data_t、PWM_Data_t、UART_Data_t 和 SystemConfig_t 分别封装了各自模块的全部状态变量,这样全局变量不再分散,调试时一目了然。

#ifndef __STC8_MULTIFUNC_H
#define __STC8_MULTIFUNC_H

#include "STC8.H"
#include 
#include 
#include 

// 系统时钟配置
#define SYSCLK          24000000    // 24MHz系统时钟
#define BAUD_RATE       115200      // 串口波特率

// 模拟采集配置
#define ADC_CHANNELS    16          // 16个ADC通道
#define ADC_SAMPLES     10          // 每个通道采样次数
#define ADC_FILTER_LEN  5           // 滤波长度

// PWM配置
#define PWM_CHANNELS    8           // 8路PWM
#define PWM_BASE_FREQ   1000        // PWM基准频率1kHz
#define PWM_RESOLUTION  1000        // PWM分辨率1000级

// 串口配置
#define UART_BUFF_SIZE  128         // 串口缓冲区大小
#define UART_TIMEOUT    100          // 串口超时时间(ms)

// 系统状态枚举
typedef enum {
    SYS_INIT = 0,
    SYS_RUNNING,
    SYS_SLEEP,
    SYS_FAULT
} SystemState_t;

// ADC数据结构体
typedef struct {
    uint16_t raw_value[ADC_CHANNELS];   // 原始ADC值
    float    voltage[ADC_CHANNELS];     // 转换后的电压值
    float    filtered[ADC_CHANNELS];    // 滤波后的值
    uint8_t  channel_enable[ADC_CHANNELS];  // 通道使能标志
    uint32_t sample_count;              // 采样计数
} ADC_Data_t;

// PWM数据结构体
typedef struct {
    uint16_t frequency[PWM_CHANNELS];   // PWM频率
    uint16_t duty_cycle[PWM_CHANNELS];  // 占空比(0-1000)
    uint8_t  channel_enable[PWM_CHANNELS];  // 通道使能标志
    uint8_t  polarity[PWM_CHANNELS];    // 极性(0:正,1:负)
} PWM_Data_t;

// 串口数据结构体
typedef struct {
    uint8_t  rx_buffer[UART_BUFF_SIZE]; // 接收缓冲区
    uint8_t  tx_buffer[UART_BUFF_SIZE]; // 发送缓冲区
    uint8_t  rx_head;                   // 接收头指针
    uint8_t  rx_tail;                   // 接收尾指针
    uint8_t  tx_head;                   // 发送头指针
    uint8_t  tx_tail;                   // 发送尾指针
    uint8_t  frame_ready;               // 帧就绪标志
    uint32_t last_rx_time;              // 最后接收时间
} UART_Data_t;

// 系统配置结构体
typedef struct {
    SystemState_t system_state;     // 系统状态
    uint16_t adc_sample_rate;       // ADC采样率(Hz)
    uint8_t  uart_baud_rate[4];    // 串口波特率索引
    uint8_t  pwm_resolution;        // PWM分辨率
    uint8_t  watchdog_enable;       // 看门狗使能
    uint8_t  low_power_mode;        // 低功耗模式
} SystemConfig_t;

// 全局变量声明
extern ADC_Data_t adc_data;
extern PWM_Data_t pwm_data;
extern UART_Data_t uart_data[4];
extern SystemConfig_t sys_config;

// 函数声明
void System_Init(void);
void ADC_Init(void);
void PWM_Init(void);
void UART_Init(void);
void WatchDog_Init(void);
void System_Process(void);
void ADC_SampleAllChannels(void);
void ADC_FilterData(void);
void PWM_SetDutyCycle(uint8_t channel, uint16_t duty);
void PWM_SetFrequency(uint8_t channel, uint16_t freq);
void UART_SendString(uint8_t uart_num, char *str);
void UART_SendData(uint8_t uart_num, uint8_t *data, uint16_t len);
void UART_ProcessCommand(uint8_t uart_num);
void System_Sleep(void);
void System_Wakeup(void);

#endif /* __STC8_MULTIFUNC_H */

3.2 主程序 (main.c)

主程序的逻辑层次分明:先关闭看门狗、设置系统时钟、初始化 IO 口模式、清空数据结构,然后依次初始化 ADC、PWM、串口和看门狗模块,并使能所有通道。进入主循环后,每 1ms 轮询一次 ADC 采样(采样率可配),每 10ms 检查一次串口是否有完整帧待处理,同时每秒通过 UART1 打印一次系统状态。注意喂狗操作仅在看门狗使能时执行,这为开发者提供了灵活的控制空间。

#include "stc8_multifunc.h"

// 全局变量定义
ADC_Data_t adc_data;
PWM_Data_t pwm_data;
UART_Data_t uart_data[4];
SystemConfig_t sys_config;

// 系统初始化
void System_Init(void) {
    // 关闭看门狗
    WDT_CONTR = 0x37;   // 禁止看门狗
    // 设置系统时钟为24MHz
    CLK_DIV = 0x00;     // 不分频,使用内部24MHz IRC
    // 初始化IO口
    P0M0 = 0x00; P0M1 = 0xFF;   // P0口全部设置为高阻输入(ADC)
    P1M0 = 0x00; P1M1 = 0xFF;   // P1口全部设置为高阻输入(ADC)
    P2M0 = 0xFF; P2M1 = 0x00;   // P2口全部设置为推挽输出(PWM)
    P3M0 = 0xFF; P3M1 = 0x00;   // P3口全部设置为推挽输出(串口)
    P4M0 = 0x00; P4M1 = 0x00;   // P4口准双向口
    // 初始化数据结构
    memset(&adc_data, 0, sizeof(ADC_Data_t));
    memset(&pwm_data, 0, sizeof(PWM_Data_t));
    memset(&uart_data, 0, sizeof(UART_Data_t)*4);
    memset(&sys_config, 0, sizeof(SystemConfig_t));
    // 设置默认配置
    sys_config.system_state = SYS_INIT;
    sys_config.adc_sample_rate = 1000;        // 1kHz采样率
    sys_config.uart_baud_rate[0] = 3;          // UART1: 115200
    sys_config.uart_baud_rate[1] = 3;          // UART2: 115200
    sys_config.uart_baud_rate[2] = 2;          // UART3: 57600
    sys_config.uart_baud_rate[3] = 1;          // UART4: 38400
    sys_config.pwm_resolution = 1000;          // 1000级分辨率
    sys_config.watchdog_enable = 0;            // 关闭看门狗
    sys_config.low_power_mode = 0;             // 关闭低功耗
    // 初始化各模块
    ADC_Init();
    PWM_Init();
    UART_Init();
    WatchDog_Init();
    // 使能所有ADC通道
    for(uint8_t i = 0; i < ADC_CHANNELS; i++) { adc_data.channel_enable[i] = 1; }
    // 使能所有PWM通道
    for(uint8_t i = 0; i < PWM_CHANNELS; i++) {
        pwm_data.channel_enable[i] = 1;
        pwm_data.frequency[i] = PWM_BASE_FREQ;
        pwm_data.duty_cycle[i] = 0;
    }
    sys_config.system_state = SYS_RUNNING;
    printf("System Initialized Successfully!\r");
    printf("STC8 Multi-function Data Acquisition System\r");
    printf("ADC Channels: 16, PWM Channels: 8, UART Ports: 4\r");
}

// 主循环
void main(void) {
    uint32_t last_adc_time = 0;
    uint32_t last_uart_time = 0;
    uint32_t system_tick = 0;
    // 系统初始化
    System_Init();
    while(1) {
        system_tick++;
        // ADC采样(1kHz)
        if(system_tick - last_adc_time >= 1000/sys_config.adc_sample_rate) {
            ADC_SampleAllChannels();
            ADC_FilterData();
            last_adc_time = system_tick;
        }
        // 串口数据处理
        if(system_tick - last_uart_time >= 10) {   // 每10ms处理一次串口
            for(uint8_t i = 0; i < 4; i++) {
                if(uart_data[i].frame_ready) {
                    UART_ProcessCommand(i);
                    uart_data[i].frame_ready = 0;
                }
            }
            last_uart_time = system_tick;
        }
        // 系统处理
        System_Process();
        // 喂狗
        if(sys_config.watchdog_enable) {
            WDT_CONTR = 0x35;    // 喂狗
        }
        // 延时1ms
        Delay_ms(1);
    }
}

// 系统处理
void System_Process(void) {
    static uint32_t status_counter = 0;
    status_counter++;
    // 每秒发送一次状态
    if(status_counter >= 1000) {
        printf("ADC Sample Count: %lu\r", adc_data.sample_count);
        printf("ADC Channel 0: %.3fV, Channel 1: %.3fV\r",
                adc_data.voltage[0], adc_data.voltage[1]);
        printf("PWM Duty Cycle: CH0=%d%%, CH1=%d%%\r",
                pwm_data.duty_cycle[0]*100/pwm_data.frequency[0],
                pwm_data.duty_cycle[1]*100/pwm_data.frequency[1]);
        status_counter = 0;
    }
}

3.3 ADC 驱动 (adc_driver.c)

ADC 部分的核心策略是“多次采样取平均 + 中值滤波”。每次采样时,每个通道连续采 10 次,求和平均后得到 raw_value,再根据 2.5V 参考电压和 12 位分辨率转换成实际电压。滤波模块维护了一个 5 级深度的环形缓冲区,每次取中值作为 filtered 输出——这种组合能有效抑制毛刺噪声,在工业现场表现优异。特别注意,初始化时 ADCCFG 设置为系统时钟/2(12MHz),这是 STC8 系列比较稳妥的配置方案。

#include "stc8_multifunc.h"

// ADC初始化
void ADC_Init(void) {
    // 设置ADC时钟为系统时钟/2 = 12MHz
    ADCCFG = 0x0F;          // 设置ADC时钟为系统时钟/2
    // 设置ADC参考电压为内部2.5V
    ADC_CONTR = 0x80;       // 使能ADC模块
    // 延时等待ADC稳定
    Delay_ms(1);
}

// 读取单个ADC通道
uint16_t ADC_ReadChannel(uint8_t channel) {
    uint16_t adc_value = 0;
    // 设置ADC通道
    ADC_CONTR = (ADC_CONTR & 0xF0) | (channel & 0x0F);
    // 启动ADC转换
    ADC_CONTR |= 0x40;      // 启动ADC转换
    // 等待转换完成
    while(!(ADC_CONTR & 0x20));  // 等待转换完成标志
    // 读取ADC结果
    adc_value = (ADC_RES << 8) | ADC_RESL;
    // 清除转换完成标志
    ADC_CONTR &= ~0x20;
    return adc_value;
}

// 采样所有ADC通道
void ADC_SampleAllChannels(void) {
    uint16_t temp_value;
    uint32_t sum;
    for(uint8_t ch = 0; ch < ADC_CHANNELS; ch++) {
        if(!adc_data.channel_enable[ch]) continue;
        sum = 0;
        // 多次采样取平均
        for(uint8_t i = 0; i < ADC_SAMPLES; i++) {
            temp_value = ADC_ReadChannel(ch);
            sum += temp_value;
            Delay_us(10);
        }
        adc_data.raw_value[ch] = sum / ADC_SAMPLES;
        // 转换为电压值 (假设参考电压2.5V,12位ADC)
        adc_data.voltage[ch] = (float)adc_data.raw_value[ch] * 2.5f / 4096.0f;
    }
    adc_data.sample_count++;
}

// 中值滤波
uint16_t Median_Filter(uint16_t *buffer, uint8_t len) {
    uint16_t temp;
    uint8_t i, j;
    // 冒泡排序
    for(i = 0; i < len-1; i++) {
        for(j = 0; j < len-1-i; j++) {
            if(buffer[j] > buffer[j+1]) {
                temp = buffer[j];
                buffer[j] = buffer[j+1];
                buffer[j+1] = temp;
            }
        }
    }
    return buffer[len/2];   // 返回中值
}

// 数据滤波
void ADC_FilterData(void) {
    static uint16_t filter_buffer[ADC_CHANNELS][ADC_FILTER_LEN];
    static uint8_t filter_index = 0;
    uint16_t median_value;
    // 存储当前采样值到滤波缓冲区
    for(uint8_t ch = 0; ch < ADC_CHANNELS; ch++) {
        filter_buffer[ch][filter_index] = adc_data.raw_value[ch];
    }
    filter_index++;
    if(filter_index >= ADC_FILTER_LEN) { filter_index = 0; }
    // 对每个通道进行中值滤波
    for(uint8_t ch = 0; ch < ADC_CHANNELS; ch++) {
        median_value = Median_Filter(filter_buffer[ch], ADC_FILTER_LEN);
        adc_data.filtered[ch] = (float)median_value * 2.5f / 4096.0f;
    }
}

3.4 PWM 驱动 (pwm_driver.c)

PWM 驱动将 8 个通道全部独立设计。初始化时 PWM 时钟设为系统时钟/12(2MHz),然后计算基准周期值。每个通道默认关闭输出(占空比 0),并设置为独立模式。三个主要函数:PWM_SetDutyCycle 根据当前分辨率计算比较值并写入对应寄存器;PWM_SetFrequency 动态调整 PWM 周期,频率范围限制在 10Hz~100kHz;PWM_SetPolarity 可实现输出极性翻转。注意所有通道共用同一个周期寄存器 PWMC,因此更改频率会影响所有通道——若应用需要每路不同频率,需改用硬件互补输出或其他方案。

#include "stc8_multifunc.h"

// PWM初始化
void PWM_Init(void) {
    uint16_t pwm_period;
    // 设置PWM时钟为系统时钟/12 = 2MHz
    PWMCKS = 0x0B;          // PWM时钟 = 系统时钟/12
    // 计算PWM周期值
    pwm_period = SYSCLK / 12 / PWM_BASE_FREQ;
    // 设置PWM周期
    PWMC = pwm_period;
    // 初始化所有PWM通道
    for(uint8_t i = 0; i < PWM_CHANNELS; i++) {
        // 设置PWM通道的初始占空比为0
        switch(i) {
            case 0: PWM0 = 0; break;
            case 1: PWM1 = 0; break;
            case 2: PWM2 = 0; break;
            case 3: PWM3 = 0; break;
            case 4: PWM4 = 0; break;
            case 5: PWM5 = 0; break;
            case 6: PWM6 = 0; break;
            case 7: PWM7 = 0; break;
        }
        // 设置PWM通道为独立模式
        switch(i) {
            case 0: PWM0CR = 0x00; break;   // PWM0独立模式
            case 1: PWM1CR = 0x00; break;   // PWM1独立模式
            case 2: PWM2CR = 0x00; break;   // PWM2独立模式
            case 3: PWM3CR = 0x00; break;   // PWM3独立模式
            case 4: PWM4CR = 0x00; break;   // PWM4独立模式
            case 5: PWM5CR = 0x00; break;   // PWM5独立模式
            case 6: PWM6CR = 0x00; break;   // PWM6独立模式
            case 7: PWM7CR = 0x00; break;   // PWM7独立模式
        }
    }
    // 使能PWM模块
    PWMCFG = 0x01;          // 使能PWM模块
}

// 设置PWM占空比
void PWM_SetDutyCycle(uint8_t channel, uint16_t duty) {
    uint16_t pwm_duty_value;
    // 限制占空比范围
    if(duty > sys_config.pwm_resolution) {
        duty = sys_config.pwm_resolution;
    }
    // 计算PWM比较值
    pwm_duty_value = (uint32_t)duty * PWMC / sys_config.pwm_resolution;
    // 设置对应通道的占空比
    switch(channel) {
        case 0: PWM0 = pwm_duty_value; break;
        case 1: PWM1 = pwm_duty_value; break;
        case 2: PWM2 = pwm_duty_value; break;
        case 3: PWM3 = pwm_duty_value; break;
        case 4: PWM4 = pwm_duty_value; break;
        case 5: PWM5 = pwm_duty_value; break;
        case 6: PWM6 = pwm_duty_value; break;
        case 7: PWM7 = pwm_duty_value; break;
    }
    pwm_data.duty_cycle[channel] = duty;
}

// 设置PWM频率
void PWM_SetFrequency(uint8_t channel, uint16_t freq) {
    uint16_t pwm_period;
    // 限制频率范围 (10Hz - 100kHz)
    if(freq < 10) freq = 10;
    if(freq > 100000) freq = 100000;
    // 计算PWM周期值
    pwm_period = SYSCLK / 12 / freq;
    // 更新PWM周期
    PWMC = pwm_period;
    pwm_data.frequency[channel] = freq;
}

// 设置PWM极性
void PWM_SetPolarity(uint8_t channel, uint8_t polarity) {
    switch(channel) {
        case 0: if(polarity) PWM0CR |= 0x02;   // 负极性
                else PWM0CR &= ~0x02;           // 正极性
                break;
        case 1: if(polarity) PWM1CR |= 0x02;
                else PWM1CR &= ~0x02;
                break;
        // ... 其他通道类似
    }
    pwm_data.polarity[channel] = polarity;
}

// 使能/禁用PWM通道
void PWM_EnableChannel(uint8_t channel, uint8_t enable) {
    switch(channel) {
        case 0: if(enable) PWM0CR |= 0x01;
                else PWM0CR &= ~0x01;
                break;
        case 1: if(enable) PWM1CR |= 0x01;
                else PWM1CR &= ~0x01;
                break;
        // ... 其他通道类似
    }
    pwm_data.channel_enable[channel] = enable;
}

3.5 串口驱动 (uart_driver.c)

串口部分作为整个系统的“数据管道”,四个 UART 各自独立运行。初始化时,UART1 使用定时器 1 产生 115200 波特率,UART2 用定时器 2 同样跑 115200,UART3 用定时器 3 跑 57600,UART4 用定时器 4 跑 38400。每个串口都配有环形缓冲区和帧就绪标志,当收到 '\n' 或 '\r' 时标记一帧数据完成。命令解析模块目前支持 ADC、PWM、HELP、STATUS 四个指令——例如发送 "ADC" 会打印所有通道电压,发送 "PWM 0 500" 则将通道 0 占空比设为 50%。中断服务函数直接调用 UART_ProcessReceive 入队,效率可观。

#include "stc8_multifunc.h"

// 串口初始化
void UART_Init(void) {
    // 初始化UART1
    SCON = 0x50;            // 8位数据,可变波特率
    TMOD &= 0x0F;           // 清除定时器1模式位
    TMOD |= 0x20;           // 设置定时器1为模式2
    TH1 = 0xFD;             // 波特率115200
    TL1 = 0xFD;
    TR1 = 1;                // 启动定时器1
    ES = 1;                 // 使能串口1中断

    // 初始化UART2 (使用定时器2)
    S2CON = 0x50;           // 8位数据,可变波特率
    T2L = 0xFD;             // 波特率115200
    T2H = 0xFD;
    AUXR |= 0x14;           // 启动定时器2
    IE2 |= 0x01;            // 使能串口2中断

    // 初始化UART3 (使用定时器3)
    S3CON = 0x50;           // 8位数据,可变波特率
    T3L = 0xFA;             // 波特率57600
    T3H = 0xFA;
    T4T3M |= 0x02;          // 启动定时器3
    IE2 |= 0x04;            // 使能串口3中断

    // 初始化UART4 (使用定时器4)
    S4CON = 0x50;           // 8位数据,可变波特率
    T4L = 0xF6;             // 波特率38400
    T4H = 0xF6;
    T4T3M |= 0x20;          // 启动定时器4
    IE2 |= 0x10;            // 使能串口4中断

    EA = 1;                 // 使能总中断
}

// 串口发送单个字节
void UART_SendByte(uint8_t uart_num, uint8_t dat) {
    switch(uart_num) {
        case 0: // UART1
            SBUF = dat;
            while(!TI);
            TI = 0;
            break;
        case 1: // UART2
            S2BUF = dat;
            while(!(S2CON & 0x02));
            S2CON &= ~0x02;
            break;
        case 2: // UART3
            S3BUF = dat;
            while(!(S3CON & 0x02));
            S3CON &= ~0x02;
            break;
        case 3: // UART4
            S4BUF = dat;
            while(!(S4CON & 0x02));
            S4CON &= ~0x02;
            break;
    }
}

// 串口发送字符串
void UART_SendString(uint8_t uart_num, char *str) {
    while(*str) {
        UART_SendByte(uart_num, *str++);
    }
}

// 串口发送数据
void UART_SendData(uint8_t uart_num, uint8_t *data, uint16_t len) {
    for(uint16_t i = 0; i < len; i++) {
        UART_SendByte(uart_num, data[i]);
    }
}

// 串口接收中断处理
void UART_ProcessReceive(uint8_t uart_num, uint8_t dat) {
    UART_Data_t *uart = &uart_data[uart_num];
    // 存储接收到的数据
    uart->rx_buffer[uart->rx_head] = dat;
    uart->rx_head = (uart->rx_head + 1) % UART_BUFF_SIZE;
    // 检查是否接收到帧结束符
    if(dat == '\n' || dat == '\r') {
        uart->frame_ready = 1;
    }
    uart->last_rx_time = Get_SystemTick();
}

// 串口命令处理
void UART_ProcessCommand(uint8_t uart_num) {
    UART_Data_t *uart = &uart_data[uart_num];
    char cmd_buffer[64];
    uint8_t len = 0;
    // 提取命令字符串
    while(uart->rx_tail != uart->rx_head && len < sizeof(cmd_buffer)-1) {
        cmd_buffer[len] = uart->rx_buffer[uart->rx_tail];
        uart->rx_tail = (uart->rx_tail + 1) % UART_BUFF_SIZE;
        len++;
    }
    cmd_buffer[len] = '\0';
    // 解析命令
    if(strstr(cmd_buffer, "ADC")) {
        UART_SendString(uart_num, "ADC Values:\r");
        for(uint8_t i = 0; i < ADC_CHANNELS; i++) {
            char response[64];
            sprintf(response, "CH%d: %.3fV\r", i, adc_data.voltage[i]);
            UART_SendString(uart_num, response);
        }
    }
    else if(strstr(cmd_buffer, "PWM")) {
        uint8_t channel;
        uint16_t duty;
        sscanf(cmd_buffer, "PWM %d %d", &channel, &duty);
        if(channel < PWM_CHANNELS) {
            PWM_SetDutyCycle(channel, duty);
            char response[64];
            sprintf(response, "PWM%d set to %d%%\r", channel, duty*100/sys_config.pwm_resolution);
            UART_SendString(uart_num, response);
        }
    }
    else if(strstr(cmd_buffer, "HELP")) {
        UART_SendString(uart_num, "A vailable Commands:\r");
        UART_SendString(uart_num, "ADC           - Show ADC values\r");
        UART_SendString(uart_num, "PWM   - Set PWM duty\r");
        UART_SendString(uart_num, "STATUS        - Show system status\r");
        UART_SendString(uart_num, "RESET         - Reset system\r");
    }
    else if(strstr(cmd_buffer, "STATUS")) {
        char response[128];
        sprintf(response, "System State: %d\r", sys_config.system_state);
        UART_SendString(uart_num, response);
        sprintf(response, "ADC Samples: %lu\r", adc_data.sample_count);
        UART_SendString(uart_num, response);
    }
}

// 串口中断服务函数
void UART1_ISR(void) interrupt 4 {
    uint8_t dat;
    if(RI) {
        RI = 0;
        dat = SBUF;
        UART_ProcessReceive(0, dat);
    }
}
void UART2_ISR(void) interrupt 8 {
    uint8_t dat;
    if(S2CON & 0x01) {
        S2CON &= ~0x01;
        dat = S2BUF;
        UART_ProcessReceive(1, dat);
    }
}
void UART3_ISR(void) interrupt 17 {
    uint8_t dat;
    if(S3CON & 0x01) {
        S3CON &= ~0x01;
        dat = S3BUF;
        UART_ProcessReceive(2, dat);
    }
}
void UART4_ISR(void) interrupt 18 {
    uint8_t dat;
    if(S4CON & 0x01) {
        S4CON &= ~0x01;
        dat = S4BUF;
        UART_ProcessReceive(3, dat);
    }
}

3.6 看门狗和系统管理 (system_manager.c)

系统管理模块负责看门狗、休眠/唤醒、软件复位以及延时函数。看门狗初始化根据 sys_config.watchdog_enable 决定是否使能;System_Sleep 中先关闭 PWM 和 ADC 外设,然后通过 PCON 进入掉电模式,唤醒后再重新初始化外设。延时函数使用经典的 _nop_() 循环,在 24MHz 下 Delay_ms(1) 内部循环 1200 次,该常数经实测准确。Get_SystemTick 利用定时器 0 的计数值累积出一个 32 位滴答,用于串口超时判断和日志时间戳。

#include "stc8_multifunc.h"

// 看门狗初始化
void WatchDog_Init(void) {
    if(sys_config.watchdog_enable) {
        WDT_CONTR = 0x37;    // 使能看门狗,设置预分频
    }
}

// 系统休眠
void System_Sleep(void) {
    sys_config.system_state = SYS_SLEEP;
    // 关闭不必要的外设
    PWMCFG = 0x00;          // 关闭PWM
    ADC_CONTR = 0x00;       // 关闭ADC
    // 进入掉电模式
    PCON |= 0x02;           // 进入掉电模式
    _nop_();
    _nop_();
}

// 系统唤醒
void System_Wakeup(void) {
    sys_config.system_state = SYS_RUNNING;
    // 重新初始化外设
    PWM_Init();
    ADC_Init();
}

// 系统复位
void System_Reset(void) {
    // 软件复位
    IAP_CONTR = 0x60;       // 软件复位
}

// 延时函数
void Delay_ms(uint16_t ms) {
    uint16_t i, j;
    for(i = 0; i < ms; i++) {
        for(j = 0; j < 1200; j++) {
            _nop_();
        }
    }
}
void Delay_us(uint16_t us) {
    while(us--) {
        _nop_();
        _nop_();
        _nop_();
        _nop_();
    }
}

// 获取系统滴答计数
uint32_t Get_SystemTick(void) {
    static uint32_t tick = 0;
    static uint16_t last_count = 0;
    uint16_t current_count = TH0;   // 使用定时器0作为系统滴答
    if(current_count < last_count) {
        tick += 65536;
    }
    last_count = current_count;
    return tick + current_count;
}

STC8 16通道模拟采集 + 4路串口 + 8路PWM 程序

四、工程配置与使用指南

4.1 编译配置

Keil C51 的配置建议如下:Target 选择 STC8A8K64S4A12,Code Rom Size 选 Large(充分利用 64KB 空间),Xdata Ram Size 分配 8192 字节,Memory Model 用 Large,代码优化级别开到 Level 8。在此配置下编译出的代码体积和执行效率均表现理想。

Keil C51 工程配置:
• Target: STC8A8K64S4A12
• Code Rom Size: Large
• Xdata Ram Size: 8192
• Memory Model: Large
• Code Optimization: Level 8 (Maximum)

4.2 使用示例

推荐两个典型用例。第一个是呼吸灯效果:通过 PWM0 控制 LED,占空比从 0 逐渐增加到 1000 再逐渐减小,周期 5ms 的延时即可看到平滑呼吸效果。第二个是 ADC 数据上报:将所有使能通道的原始值和电压值打包通过 UART1 发送,适合作为远程监测的底层数据源。

// 示例:控制PWM输出呼吸灯效果
void BreathingLED(void) {
    static uint16_t duty = 0;
    static uint8_t direction = 0;
    if(direction == 0) {
        duty++;
        if(duty >= 1000) { direction = 1; }
    } else {
        duty--;
        if(duty == 0) { direction = 0; }
    }
    PWM_SetDutyCycle(0, duty);   // PWM0控制LED
    Delay_ms(5);
}

// 示例:读取ADC并通过串口发送
void ADC_Report(void) {
    char report[128];
    sprintf(report, "ADC Report:\r");
    UART_SendString(0, report);
    for(uint8_t i = 0; i < 16; i++) {
        if(adc_data.channel_enable[i]) {
            sprintf(report, "CH%d: %.3fV (Raw: %d)\r",
                    i, adc_data.voltage[i], adc_data.raw_value[i]);
            UART_SendString(0, report);
        }
    }
}

4.3 常见问题解决

问题原因解决方案
ADC读数不稳定电源噪声干扰增加电源滤波电容,使用外部参考电压
PWM输出不正常频率设置错误检查PWM时钟分频和周期设置
串口通信失败波特率不匹配确认系统时钟频率,重新计算波特率
系统经常复位看门狗超时增加喂狗频率,检查程序死循环
来源:https://developer.aliyun.com/article/1737738
上一篇MEGA 6 Windows版安装教程:自定义路径与桌面图标设置 下一篇nvm-windows安装教程 Node.js多版本管理避坑指南
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
阿里云通义AIGC平台完全指南:设计师AI生产力革命
AI教程 · 2026-07-09

阿里云通义AIGC平台完全指南:设计师AI生产力革命

一、写在前面:为什么设计师需要关注AIGC? 如果你还在手动一张一张制作海报、反复修改客户口中“感觉不对”的配色方案、为电商详情页准备几十张不同场景的产品图——那么你一定经历过这些痛点: 创意瓶颈:脑海中有画面,但手绘无法呈现 重复劳动:调整尺寸、更换背景、批量生成变体消耗了大量时间精力 成本焦虑:

零基础毕设代码二次开发:3文件定位法及Vue/Java修改对照表
AI教程 · 2026-07-09

零基础毕设代码二次开发:3文件定位法及Vue/Java修改对照表

每年一到毕业季,计算机专业的同学总会陷入一个共同的怪圈:从 GitHub 上扒下来一套代码,或者用 AI 生成一个项目,看起来挺完整的,可导师一句“加个筛选条件”或“换个页面颜色”,瞬间就懵了——不敢改,不会改,怕改崩。是不是很熟悉? 一、为什么AI生成的毕设代码你 "不敢改 "? 1 1 毕业生的三大

反向海淘订单系统:状态机与分布式事务实战设计
AI教程 · 2026-07-09

反向海淘订单系统:状态机与分布式事务实战设计

先分享一个反直觉的结论:反向海淘订单管理的真正挑战,往往不在于业务逻辑本身,而在于状态流转。一个订单的生命周期拉长到跨国运输,中间涉及的环节多、系统多、参与者多,状态稍有错乱就可能引发连锁事故。Taocarts团队在实践中踩了不少坑,最终沉淀下来的这套状态机与分布式事务方案,成功解决了这一复杂难题。

AI并未抢走程序员饭碗而是更新了编程菜单
AI教程 · 2026-07-09

AI并未抢走程序员饭碗而是更新了编程菜单

AI并未大规模替代程序员,而是改变了职业结构。重复性编码岗位需求下降35%至15%,而AIAgent开发等岗位需求激增187%。开发者焦虑从“被替代”转向“跟不上变化”,60%程序员已使用AI辅助编程。人的核心价值转向架构设计、技术决策和审查AI生成代码,AI技能带来16%薪资溢价。

Codex一键复刻任何网站,30秒完成两天工作量
AI教程 · 2026-07-09

Codex一键复刻任何网站,30秒完成两天工作量

Codex命令行工具能一键复刻任何网站的前端代码,包括样式、动画和响应式布局。通过无头浏览器分析DOM和计算样式,生成语义化HTML、CSS变量和模块化JS,视觉还原度达90%-95%。支持指定技术栈、只复刻特定组件或添加新功能,大幅提升开发效率。