为什么keil中程序正常无错误,但proteus中大多数引脚为灰色且无法实现功能?

新手上路,请多包涵

新手使用keil和proteus联合仿真一个太阳能智能逐日系统,keil中程序正常无错误,但proteus中大多数引脚为灰色且无法实现功能:pcf8591采集的数据显示在lcd1上,按钮B1切换电机的自动与手动旋转,按钮B2和B3控制电机正反转proteus的系统仿真图

main.C的代码:

#include "stm32f10x.h"
#include <stdio.h>
#include "sys.h"
#include "lcd.h"
#include "i2c.h"
#include "delay.h"

// 定义按键和电机引脚
#define KEY_AUTO GPIO_Pin_6  // B1
#define KEY_FWD  GPIO_Pin_7  // B2
#define KEY_REV  GPIO_Pin_8  // B3

static uint8_t auto_mode = 0;  // 0:手动模式, 1:自动模式

// 按键扫描函数
uint8_t Key_Scan(void)
{
    static uint8_t key_up = 1;
    if(key_up && (!GPIO_ReadInputDataBit(GPIOB, KEY_AUTO) || 
                  !GPIO_ReadInputDataBit(GPIOB, KEY_FWD) || 
                  !GPIO_ReadInputDataBit(GPIOB, KEY_REV)))
    {
        Delay_ms(20);  // 消抖
        key_up = 0;
        if(!GPIO_ReadInputDataBit(GPIOB, KEY_AUTO)) return 1;
        if(!GPIO_ReadInputDataBit(GPIOB, KEY_FWD)) return 2;
        if(!GPIO_ReadInputDataBit(GPIOB, KEY_REV)) return 3;
    }
    else if(GPIO_ReadInputDataBit(GPIOB, KEY_AUTO) && 
            GPIO_ReadInputDataBit(GPIOB, KEY_FWD) && 
            GPIO_ReadInputDataBit(GPIOB, KEY_REV))
    {
        key_up = 1;
    }
    return 0;
}

// 显示函数
void Display_Data(uint8_t ldr1, uint8_t ldr2)
{
    char buffer[16];
    
    LCD_WriteCmd(0x80);  // 第一行
    sprintf(buffer, "LDR1:%3d %s", ldr1, auto_mode ? "AUTO" : "MANU");
    LCD_DisplayString(buffer);
    
    LCD_WriteCmd(0xC0);  // 第二行
    sprintf(buffer, "LDR2:%3d", ldr2);
    LCD_DisplayString(buffer);
}

int main(void)
{
    uint8_t ldr1_value, ldr2_value;
    uint8_t key;
    
    // 系统初始化
    SystemInit();        // STM32系统初始化
    System_Init();       // 使能GPIO和I2C时钟
    Delay_init();        // 初始化延时功能
    
    // 等待系统稳定
    Delay_ms(100);
    
    // 外设初始化
    LCD_Init();          // 初始化LCD
    I2C_Configuration(); // 初始化I2C和电机控制引脚
    
    // 清屏并显示初始界面
    LCD_Clear();         // 清屏
    Display_Data(0, 0);  // 显示初始值
    
    while(1)
    {
        // 读取按键
        key = Key_Scan();
        if(key == 1)
        {
            auto_mode = !auto_mode;  // 切换模式
            Delay_ms(10);
        }
        
        // 读取光敏电阻值
        ldr1_value = PCF8591_ReadAD(0);  // 读取LDR1
        Delay_ms(10);
        ldr2_value = PCF8591_ReadAD(1);  // 读取LDR2
        Delay_ms(10);
        
        // 显示数据
        Display_Data(ldr1_value, ldr2_value);
        
        // 根据模式控制电机
        if(auto_mode)
        {
            // 自动模式:根据光敏电阻值控制
            if(ldr1_value > ldr2_value + 20)
            {
                Motor_Forward();
            }
            else if(ldr2_value > ldr1_value + 20)
            {
                Motor_Reverse();
            }
        }
        else
        {
            // 手动模式:按键控制
            if(key == 2) Motor_Forward();
            if(key == 3) Motor_Reverse();
        }
        
        Delay_ms(10);
    }
}

lcd.C:

#include "lcd.h"
#include "delay.h"

// LCD初始化
void LCD_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA和GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置LCD数据引脚 PA0-PA7
    GPIO_InitStructure.GPIO_Pin = 0xFF;  // PA0-PA7
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置LCD控制引脚 PB0-PB2
    GPIO_InitStructure.GPIO_Pin = LCD_RS | LCD_RW | LCD_EN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // LCD初始化过程
    Delay_ms(15);
    LCD_WriteCmd(0x38);  // 功能设置:8位数据接口,2行显示,5x7点阵
    Delay_ms(5);
    LCD_WriteCmd(0x38);
    Delay_ms(5);
    LCD_WriteCmd(0x38);
    
    LCD_WriteCmd(LCD_DISPLAY_ON);  // 显示开,光标关,不闪烁
    LCD_WriteCmd(LCD_CLEAR);       // 清屏
    Delay_ms(2);
    LCD_WriteCmd(LCD_MODE);        // 设置输入模式
}

// 写命令
void LCD_WriteCmd(uint8_t cmd)
{
    GPIO_ResetBits(GPIOB, LCD_RS);  // RS=0,命令
    GPIO_ResetBits(GPIOB, LCD_RW);  // RW=0,写
    GPIOA->ODR = cmd;               // 写入命令
    
    GPIO_SetBits(GPIOB, LCD_EN);    // EN=1
    Delay_us(1);                    // 延时
    GPIO_ResetBits(GPIOB, LCD_EN);  // EN=0
    Delay_ms(2);                    // 等待命令执行完成
}

// 写数据
void LCD_WriteData(uint8_t data)
{
    GPIO_SetBits(GPIOB, LCD_RS);    // RS=1,数据
    GPIO_ResetBits(GPIOB, LCD_RW);  // RW=0,写
    GPIOA->ODR = data;              // 写入数据
    
    GPIO_SetBits(GPIOB, LCD_EN);    // EN=1
    Delay_us(1);                    // 延时
    GPIO_ResetBits(GPIOB, LCD_EN);  // EN=0
    Delay_ms(2);                    // 等待数据写入完成
}

// 显示字符串
void LCD_DisplayString(char *str)
{
    while(*str)
    {
        LCD_WriteData(*str++);
    }
}

// 清屏
void LCD_Clear(void)
{
    LCD_WriteCmd(LCD_CLEAR);
    Delay_ms(2);
}

// 设置光标位置
void LCD_SetCursor(uint8_t x, uint8_t y)
{
    uint8_t addr;
    if(y == 0)
        addr = 0x80 + x;
    else
        addr = 0xC0 + x;
    LCD_WriteCmd(addr);
}

lcd.H:

#ifndef __LCD_H
#define __LCD_H

#include "stm32f10x.h"

// LCD引脚定义
#define LCD_RS  GPIO_Pin_0  // PB0
#define LCD_RW  GPIO_Pin_1  // PB1
#define LCD_EN  GPIO_Pin_2  // PB2

// LCD命令
#define LCD_CLEAR       0x01  // 清屏
#define LCD_HOME        0x02  // 光标归位
#define LCD_MODE        0x06  // 光标右移,显示不移动
#define LCD_DISPLAY_ON  0x0C  // 显示开,光标关,不闪烁
#define LCD_FUNC_SET    0x38  // 8位数据接口,2行显示,5x7点阵

// 函数声明
void LCD_Init(void);
void LCD_WriteCmd(uint8_t cmd);
void LCD_WriteData(uint8_t data);
void LCD_DisplayString(char *str);
void LCD_Clear(void);
void LCD_SetCursor(uint8_t x, uint8_t y);

#endif

delay.C:

#include "delay.h"
#include "stm32f10x.h"

static uint8_t fac_us = 0;
static uint16_t fac_ms = 0;

// 初始化延时函数
void Delay_init(void)
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
    fac_us = SystemCoreClock / 8000000;
    fac_ms = (uint16_t)fac_us * 1000;
}

// 微秒延时
void Delay_us(uint32_t us)
{
    uint32_t temp;
    SysTick->LOAD = us * fac_us;
    SysTick->VAL = 0x00;
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    do
    {
        temp = SysTick->CTRL;
    }while((temp & 0x01) && !(temp & (1<<16)));
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    SysTick->VAL = 0X00;
}

// 毫秒延时
void Delay_ms(uint32_t ms)
{
    uint32_t temp;
    SysTick->LOAD = (uint32_t)ms * fac_ms;
    SysTick->VAL = 0x00;
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    do
    {
        temp = SysTick->CTRL;
    }while((temp & 0x01) && !(temp & (1<<16)));
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    SysTick->VAL = 0X00;
}

delay.H:

#ifndef __DELAY_H
#define __DELAY_H

#include "stm32f10x.h"

void Delay_init(void);
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);

#endif 

i2c.C:

#include "i2c.h"
#include "delay.h"

// I2C引脚定义
#define SCL_PIN GPIO_Pin_3    // PB3
#define SDA_PIN GPIO_Pin_4    // PB4
#define MOTOR_A GPIO_Pin_10   // PB10
#define MOTOR_B GPIO_Pin_11   // PB11
#define MOTOR_C GPIO_Pin_12   // PB12
#define MOTOR_D GPIO_Pin_13   // PB13

// I2C操作函数
#define SDA_H GPIO_SetBits(GPIOB, SDA_PIN)
#define SDA_L GPIO_ResetBits(GPIOB, SDA_PIN)
#define SCL_H GPIO_SetBits(GPIOB, SCL_PIN)
#define SCL_L GPIO_ResetBits(GPIOB, SCL_PIN)
#define SDA_READ GPIO_ReadInputDataBit(GPIOB, SDA_PIN)

// I2C基本操作函数
void I2C_Start(void)
{
    SDA_H;
    SCL_H;
    Delay_us(4);
    SDA_L;
    Delay_us(4);
    SCL_L;
}

void I2C_Stop(void)
{
    SDA_L;
    SCL_H;
    Delay_us(4);
    SDA_H;
    Delay_us(4);
}

uint8_t I2C_WaitAck(void)
{
    uint8_t ucErrTime=0;

    SDA_H;
    Delay_us(1);
    SCL_H;
    Delay_us(1);
    
    while(SDA_READ)
    {
        ucErrTime++;
        if(ucErrTime>250)
        {
            I2C_Stop();
            return 1;
        }
    }
    SCL_L;
    return 0;
}

void I2C_Ack(void)
{
    SCL_L;
    SDA_L;
    Delay_us(2);
    SCL_H;
    Delay_us(2);
    SCL_L;
}

void I2C_NAck(void)
{
    SCL_L;
    SDA_H;
    Delay_us(2);
    SCL_H;
    Delay_us(2);
    SCL_L;
}

void I2C_SendByte(uint8_t txd)
{
    uint8_t t;   
    SCL_L;
    for(t=0;t<8;t++)
    {
        if((txd&0x80)>>7)
            SDA_H;
        else
            SDA_L;
        txd<<=1;    
        Delay_us(2);
        SCL_H;
        Delay_us(2); 
        SCL_L;    
        Delay_us(2);
    }    
}

uint8_t I2C_ReadByte(unsigned char ack)
{
    unsigned char i,receive=0;
    for(i=0;i<8;i++ )
    {
        SCL_H;
        Delay_us(2);
        receive<<=1;
        if(SDA_READ)receive++;   
        SCL_L;
        Delay_us(1); 
    }                     
    if (!ack)
        I2C_NAck();
    else
        I2C_Ack();
    return receive;
}

void I2C_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 配置I2C引脚
    GPIO_InitStructure.GPIO_Pin = SCL_PIN | SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 配置电机控制引脚
    GPIO_InitStructure.GPIO_Pin = MOTOR_A | MOTOR_B | MOTOR_C | MOTOR_D;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 设置初始电平
    GPIO_SetBits(GPIOB, SCL_PIN | SDA_PIN);
}

// 电机控制函数
void Motor_Forward(void)
{
    static const uint8_t step_sequence[] = {0x01, 0x02, 0x04, 0x08};
    static uint8_t step_index = 0;
    
    uint8_t output = step_sequence[step_index];
    GPIO_Write(GPIOB, (uint16_t)((GPIO_ReadOutputData(GPIOB) & 0xF3FF) | 
                                ((output & 0x0F) << 10)));
    
    step_index = (step_index + 1) % 4;
    Delay_ms(5);
}

void Motor_Reverse(void)
{
    static const uint8_t step_sequence[] = {0x08, 0x04, 0x02, 0x01};
    static uint8_t step_index = 0;
    
    uint8_t output = step_sequence[step_index];
    GPIO_Write(GPIOB, (uint16_t)((GPIO_ReadOutputData(GPIOB) & 0xF3FF) | 
                                ((output & 0x0F) << 10)));
    
    step_index = (step_index + 1) % 4;
    Delay_ms(5);
}

// PCF8591相关函数
uint8_t PCF8591_ReadAD(uint8_t channel)
{
    uint8_t value;
    
    // 发送起始信号
    I2C_Start();
    
    // 发送设备地址和写命令
    I2C_SendByte(PCF8591_ADDR);
    I2C_WaitAck();
    
    // 发送控制字节
    I2C_SendByte(0x40 | channel);
    I2C_WaitAck();
    
    // 重新开始
    I2C_Start();
    
    // 发送设备地址和读命令
    I2C_SendByte(PCF8591_ADDR | 0x01);
    I2C_WaitAck();
    
    // 读取数据
    value = I2C_ReadByte(0);
    
    // 发送停止信号
    I2C_Stop();
    
    return value;
}

i2c.H:

#ifndef __I2C_H
#define __I2C_H

#include "stm32f10x.h"

#define PCF8591_ADDR 0x90  // PCF8591地址

// I2C基本操作函数
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(uint8_t txd);
uint8_t I2C_ReadByte(unsigned char ack);
uint8_t I2C_WaitAck(void);
void I2C_Ack(void);
void I2C_NAck(void);

// 外设控制函数
void I2C_Configuration(void);
uint8_t PCF8591_ReadAD(uint8_t channel);
void Motor_Forward(void);
void Motor_Reverse(void);

#endif

sys.C:

#include "sys.h"

void System_Init(void)
{
    // 使能APB2外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | 
                          RCC_APB2Periph_GPIOB | 
                          RCC_APB2Periph_AFIO, ENABLE);
    
    // 配置系统时钟
    RCC_HSEConfig(RCC_HSE_ON);  // 使能外部高速晶振
    while(RCC_WaitForHSEStartUp() != SUCCESS);  // 等待HSE就绪
    
    // 配置PLL
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);  // PLLCLK = HSE * 9 = 72MHz
    RCC_PLLCmd(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);   // 等待PLL就绪
    
    // 配置系统时钟
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);  // 选择PLL作为系统时钟
    while(RCC_GetSYSCLKSource() != 0x08);        // 等待PLL成为系统时钟源
    
    // 配置AHB、APB1、APB2分频
    RCC_HCLKConfig(RCC_SYSCLK_Div1);    // AHB  = SYSCLK
    RCC_PCLK1Config(RCC_HCLK_Div2);     // APB1 = HCLK/2
    RCC_PCLK2Config(RCC_HCLK_Div1);     // APB2 = HCLK
}

sys.H:

#ifndef __SYS_H
#define __SYS_H

#include "stm32f10x.h"

// 系统初始化函数
void System_Init(void);

// 位带操作相关宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

// GPIO地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 

#define GPIOA_IDR_Addr    (GPIOA_BASE+8)  //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8)  //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8)  //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8)  //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8)  //0x40011808 

#endif 

反复查看引脚定义,系统初始化,外接上拉电阻均无用,希望有大佬能帮帮忙看看哪里出了错误该怎么修改

阅读 347
avatarAI BotBETA

问题原因分析及解决方案:

从代码和仿真现象分析,主要存在以下四个关键问题需要排查:


1. 硬件引脚映射不匹配(核心问题)

现象: Proteus中引脚显示灰色说明GPIO未正确配置或未输出有效电平
问题点:

  • LCD数据端口冲突: 代码中LCD数据端口使用PA0-PA7,而STM32的JTAG调试端口默认占用PA13-PA15,导致PA端口功能冲突
  • 电机控制引脚未初始化: 代码中使用PB10-PB13控制电机,但System_Init()中未使能GPIOB时钟
    解决方法:

    // 修改sys.c中的System_Init函数:
    void System_Init(void)
    {
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | 
                            RCC_APB2Periph_GPIOB | 
                            RCC_APB2Periph_AFIO | 
                            RCC_APB2Periph_AFIO, ENABLE); // 补充GPIOB使能
      
      // 关闭JTAG调试端口(关键!)
      GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); 
    }

2. I2C时序配置问题

现象: PCF8591无法正常读取数据
问题点:

  • Proteus的I2C时序要求更严格
  • 代码中缺少ACK检测和重试机制
    解决方法:

    // 修改i2c.c中的PCF8591_ReadAD函数:
    uint8_t PCF8591_ReadAD(uint8_t channel)
    {
      uint8_t value;
      uint8_t retry = 0;
      
      do {
          I2C_Start();
          if(I2C_SendByte(PCF8591_ADDR) && retry++  5) {
              Delay_ms(10);
              continue;
          }
          // ... 原有代码 ...
      } while(retry  5);
      
      return value;
    }

3. Proteus外设配置问题

需要检查的仿真设置:

  1. STM32时钟树配置必须与代码匹配(72MHz HSE)
    Proteus时钟配置示意图
  2. LCD1602需设置为4位模式(即使代码是8位)
  3. PCF8591需添加外部上拉电阻(4.7KΩ到VCC)
  4. 电机驱动需接续流二极管

4. 按键扫描逻辑优化

现象: 手动模式按键响应不灵敏
优化建议:

// 修改Key_Scan函数:
uint8_t Key_Scan(void)
{
    static uint8_t last_key = 0;
    uint8_t current_key = 0;
    
    if(!GPIO_ReadInputDataBit(GPIOB, KEY_AUTO)) current_key |= 0x01;
    if(!GPIO_ReadInputDataBit(GPIOB, KEY_FWD)) current_key |= 0x02;
    if(!GPIO_ReadInputDataBit(GPIOB, KEY_REV)) current_key |= 0x04;

    if(current_key != last_key){
        Delay_ms(20); // 消抖
        last_key = current_key;
        if(current_key) return current_key;
    }
    return 0;
}

完整验证步骤:

  1. 在Proteus中右键STM32 -> Edit Properties

    • 设置External Crystal为8MHz
    • 勾选"Use DSW-Debug"
  2. 添加4.7KΩ上拉电阻到I2C总线
  3. 重新编译并加载hex文件后启动仿真
  4. 使用Proteus自带的逻辑分析仪观察GPIO波形

调试技巧:

  • 在GPIOB寄存器窗口观察引脚输出状态
  • 使用Debug->Watch Window监控auto_mode变量
  • 右键PCF8591选择"Terminal"查看ADC转换值

经过以上修改,应该可以解决Proteus中引脚灰色不工作的问题。如果仍存在问题,建议逐步注释掉LCD和电机驱动代码,分模块调试各功能。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏