目录

银杏科技有限公司旗下技术文档发布平台
技术支持电话0379-69926675-801
技术支持邮件Gingko@vip.163.com
版本 日期 作者 修改内容
V1.0 2020-07-29 gingko 初次建立

STM32CubeMX教程五十六——Modbus-RTU实验

1.在主界面选择File–>New Project 或者直接点击ACCEE TO MCU SELECTOR 2.出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置 在搜索栏的下面,提供的各 种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号为:STM32H750IBKx。 3.配置RCC,使用外部时钟源 4.时基源选择SysTick 5.将PA10,PB7,PB8设置为GPIO_Output 6.引脚模式配置 7.配置串口 在NVIC Settings一栏使能接收中断 引脚配置 8.配置ADC 9.时钟源设置,选择外部高速时钟源,配置为最大主频 10.工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可 IDE我们使用的是 MDK V5.27 11.点击Code Generator,进行进一步配置

12.然后点击GENERATE CODE 创建工程 创建成功,打开工程。



实验五十六:Modbus-RTU实验——电源温度监控

一、实验目的与意义

  1. 了解STM32的UART结构。
  2. 了解STM32的UART特征。
  3. 掌握STM32的UART的使用方法。
  4. 掌握STM32的ADC的使用方法。
  5. 掌握RS-485的使用方法。
  6. 掌握Modbus-RTU的使用方法。
  7. 掌握KEIL MDK 集成开发环境使用方法。

二、实验设备及平台

  1. iCore4TX 双核心板点击购买
  2. iCore4TX 扩展底板。
  3. JLINK(或相同功能)仿真器。点击购买
  4. Micro USB线缆。
  5. Keil MDK 开发平台。
  6. STM32CubeMX开发平台。
  7. 装有WIN XP(及更高版本)系统的计算机。

三、实验原理

1.MODBUS协议介绍

层数 OSI模型 对应协议或硬件
7 应用层 Modbus协议
6 表示层
5 会话层
4 传输层
3 网络层
2 数据链路层 Modbus串行链路协议
1 物理层 RS-485/RS-232

RTU模式每个字节(11位)的格式为:

子字节地址 功能代码 数据 CRC
1字节 1字节 0到252字节 2字节

* Modbus报文RTU帧

2.RS-485介绍

3.ADC介绍

4.74HC4051介绍

四、实验程序

1.主函数

int main(void)
{
    int i;
    unsigned short int temp[10] = {0};
    HAL_Init();
    SystemClock_Config();
    i2c.initialize();
    axp152.initialize();
    axp152.set_dcdc1(3500);//[ARM & FPGA]
    axp152.set_dcdc2(1200);//[FPGA INT]
    axp152.set_dcdc3(3300);//[DCOUT3]
    axp152.set_dcdc4(3300);//[DCOUT4]
 
    axp152.set_aldo1(3300);//[BK3]
    axp152.set_aldo2(3300);//[ALDOOUT2]
    axp152.set_dldo1(3300);//[BK0]
    axp152.set_dldo2(3300);//[BK1]
    HAL_Delay(200);
 
 
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_ADC1_Init();
    MX_ADC3_Init();
 
    usart1.initialize(115200);
 
  while (1)
  {
        if(_500ms_flag == 1){
            _500ms_flag = 0;
            my_adc.read(0);
            my_adc.read_mux();
            temp[0] = my_adc.value[0] * 6 * 100;  //5V监控 放大100倍
            temp[1] = my_adc.value[5] / 2 * 1000.;//电流监控,取整
            temp[2] = my_adc.value[7] * 2 * 100;  //3.3V监控 放大100倍
            temp[3] = my_adc.value[4] * 2 * 100;  //2.5V监控 放大100倍
            temp[4] = my_adc.value[6] * 100;      //1.2V监控 放大100倍
            temp[5] = my_adc.value[2] * 2 * 100;  //BK3监控 放大100倍
            temp[6] = my_adc.value[1] * 2 * 100;  //BK4监控 放大100倍
            temp[7] = my_adc.value[3] * 2 * 100;  //BK5监控 放大100倍
            temp[8] = my_adc.value[8] * 2 * 100;  //BK7监控 放大100倍
            temp[9] = lm75.read()*100;            //温度监控 放大100倍
            for(i = 0;i < 10;i ++){
                hold_reg[2*i + 0] = temp[i] >> 8;
                hold_reg[2*i + 1] = temp[i] & 0xFF;
            }
        }
        if(_100ms_flag == 1){
            _100ms_flag = 0;
            modbus.process();
        }
  }
}

2.Modbus相关函数

static char process(void)
{
  unsigned short temp;
  unsigned char receive_buffer_temp[140];
  int counter_temp;
 
  //crc
  if(usart1.counter < 2)return 0;
  counter_temp = usart1.counter;
  usart1.counter = 0;
 
  memcpy(receive_buffer_temp,usart1.receive_buffer,counter_temp);
  memset(usart1.receive_buffer,0,counter_temp);
 
  temp = receive_buffer_temp[counter_temp - 1] << 8|receive_buffer_temp[counter_temp - 2];
  if(crc(receive_buffer_temp,counter_temp - 2) == temp)mb_rsq_pdu(receive_buffer_temp,counter_temp);
 
  return 0;
}
 
static int mb_rsq_pdu(unsigned char *receive_buffer_temp,int counter_temp)
 {
   if(receive_buffer_temp[0] == mb_slave_address){
     switch(receive_buffer_temp[1]){
       case 1:
               function_1(receive_buffer_temp);
               break;
       case 2:
               function_2(receive_buffer_temp);
               break;
       case 3:
               function_3(receive_buffer_temp);
               break;
       case 4:
               function_4(receive_buffer_temp);
               break;
       case 5:
               function_5(receive_buffer_temp,counter_temp);
               break;
       case 6:
               function_6(receive_buffer_temp,counter_temp);
               break;
       default :
               mb_excep_rsq_pdu(receive_buffer_temp,1);
               break;
     }
  }else if(receive_buffer_temp[0] == 0){
      broadcast(receive_buffer_temp);
    }
  return 0;
 }
 
static int function_1(unsigned char *receive_buffer_temp)
{
  int i;
  unsigned short cnt;
  unsigned short coil_num;
  unsigned short start_address;
  unsigned short crc_value;
  int temp;
 
  start_address = (receive_buffer_temp[2] << 8) | receive_buffer_temp[3];
 
  coil_num = receive_buffer_temp[4] << 8| receive_buffer_temp[5];
 
  if((start_address + coil_num) > 255){
    mb_excep_rsq_pdu(receive_buffer_temp,2);
    return 1;
  }
 
  receive_buffer_temp[2] = ((coil_num % 8 )? (coil_num / 8 + 1) : (coil_num / 8));
  cnt = receive_buffer_temp[2] + 5;
  if(coil_num % 8){
    if(coil_num < 8){
      for(i = 0;i < coil_num;i ++)temp |= 1 << i;
      receive_buffer_temp[3] = ((coil[start_address / 8]) >> (start_address % 8) | (coil[start_address / 8 + 1]) << (8 - (start_address % 8))) & temp;
    }else {
      for(i = 0;i < receive_buffer_temp[2] - 1;i++)receive_buffer_temp[3 + i] = (coil[i + start_address / 8]) >> (start_address % 8) | (coil[i + start_address / 8 + 1]) << (8 - (start_address % 8));
      receive_buffer_temp[3 + i] = (coil[i + start_address / 8] << ((8 - (coil_num % 8 - start_address % 8) % 8)) & 0xff) >> (8 - (coil_num % 8));
    }
  }else {
      for(i = 0;i < receive_buffer_temp[2];i++)receive_buffer_temp[3 + i] = (coil[i + start_address / 8]) >> (start_address % 8) | (coil[i + start_address / 8 + 1]) << (8 - (start_address % 8));
  }
 
  crc_value = crc(receive_buffer_temp,cnt - 2);
  receive_buffer_temp[cnt - 2] = crc_value & 0x00ff;
  receive_buffer_temp[cnt - 1] = (crc_value >> 8) & 0xff;
 
  for(i = 0;i < cnt;i++)usart1.send_byte(receive_buffer_temp[i]);
 
  return 0;
}
 
static int function_3(unsigned char *receive_buffer_temp)
{  
  int i;
  int cnt;
  unsigned short int start_address;
  unsigned short int crc_value;
 
  start_address = (receive_buffer_temp[2] << 8) | receive_buffer_temp[3];
 
  receive_buffer_temp[2] = receive_buffer_temp[5] * 2;
 
  if(receive_buffer_temp[2] > 100){ //判断最大传输量,防止越界
    mb_excep_rsq_pdu(receive_buffer_temp,2);
    return 1;
  }
 
  if((start_address * 2 + receive_buffer_temp[2]) > 512){
    mb_excep_rsq_pdu(receive_buffer_temp,2);
    return 1;
  }
 
  cnt = receive_buffer_temp[2] + 5;
  for(i = 0;i < receive_buffer_temp[2];i++)receive_buffer_temp[i + 3] = hold_reg[start_address * 2 + i];
 
  crc_value = crc(receive_buffer_temp,cnt - 2);
  receive_buffer_temp[cnt - 2] = crc_value & 0x00ff;
  receive_buffer_temp[cnt - 1] = (crc_value >> 8) & 0xff;
 
  for(i = 0;i < cnt;i++)usart1.send_byte(receive_buffer_temp[i]);
 
  return 0;
}
 
static int function_5(unsigned char *receive_buffer_temp,int counter_temp)
{
  int i;
  unsigned short start_address;
 
  start_address = (receive_buffer_temp[2] << 8) | receive_buffer_temp[3];
 
  if(start_address > 255){
    mb_excep_rsq_pdu(receive_buffer_temp,2);
    return 1;
  }
 
  if((receive_buffer_temp[4] == 0xff) && (receive_buffer_temp[5] == 0x00)){
     coil[(start_address / 8)] |= 1 << start_address % 8;
  }else if((receive_buffer_temp[4] == 0x00) && (receive_buffer_temp[5] == 0x00)){
     coil[(start_address / 8)] &= ~(1 << start_address % 8);
  }else {
     mb_excep_rsq_pdu(receive_buffer_temp,3);
  }
 
  for(i = 0;i < counter_temp;i++){
     usart1.send_byte(receive_buffer_temp[i]); 
  }
  return 0;

3.74HC4051通道配置

//定义74HC4051片选管脚
#define SEL_A_ON  HAL_GPIO_WritePin(GPIOI, SEL_A_Pin, GPIO_PIN_SET)
#define SEL_A_OFF HAL_GPIO_WritePin(GPIOI, SEL_A_Pin, GPIO_PIN_RESET)
 
#define SEL_B_ON  HAL_GPIO_WritePin(GPIOI, SEL_B_Pin, GPIO_PIN_SET)
#define SEL_B_OFF HAL_GPIO_WritePin(GPIOI, SEL_B_Pin, GPIO_PIN_RESET)
 
#define SEL_C_ON  HAL_GPIO_WritePin(GPIOI, SEL_C_Pin, GPIO_PIN_SET)
#define SEL_C_OFF HAL_GPIO_WritePin(GPIOI, SEL_C_Pin, GPIO_PIN_RESET)
//选择测量BK4时片选脚状态
#define CHANNEL_0_ON SEL_C_OFF;\
                     SEL_B_OFF;\
                     SEL_A_OFF
//选择测量BK3时片选脚状态
#define CHANNEL_1_ON SEL_C_OFF;\
                     SEL_B_OFF;\
                     SEL_A_ON
//选择测量BK5时片选脚状态
#define CHANNEL_2_ON SEL_C_OFF;\
                     SEL_B_ON;\
                     SEL_A_OFF      
//选择测量2.5V时片选脚状态
#define CHANNEL_3_ON SEL_C_OFF;\
                     SEL_B_ON;\
                     SEL_A_ON
//选择测量输入电流时片选脚状态
#define CHANNEL_4_ON SEL_C_ON;\
                     SEL_B_OFF;\
                     SEL_A_OFF
//选择测量1.2V时片选脚状态
#define CHANNEL_5_ON SEL_C_ON;\
                     SEL_B_OFF;\
                     SEL_A_ON
//选择测量3.3V时片选脚状态
#define CHANNEL_6_ON SEL_C_ON;\
                     SEL_B_ON;\
                     SEL_A_OFF  
//选择测量BK7时片选脚状态
#define CHANNEL_7_ON SEL_C_ON;\
                     SEL_B_ON;\
                     SEL_A_ON

4.ADC初始化函数

void MX_ADC1_Init(void)
{
  ADC_MultiModeTypeDef multimode = {0};
  ADC_ChannelConfTypeDef sConfig = {0};
  hadc1.Instance = ADC1; 
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV16;  //16分频
  hadc1.Init.Resolution = ADC_RESOLUTION_16B;  //ADC转换分辨率16位
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;  //非扫描模式
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;  //关闭 EOC 中断
  hadc1.Init.LowPowerAutoWait = DISABLE;  //自动低功耗关闭
  hadc1.Init.ContinuousConvMode = DISABLE;  //关闭连续转换
  hadc1.Init.NbrOfConversion = 1;	//1个转换在规则序列中
  hadc1.Init.DiscontinuousConvMode = DISABLE;  //禁止不连续采样模式
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;  //软件触发
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;  //禁止触发检测
  hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; //存到DR寄存器
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;  //溢出保留上次转换数据
  hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;  //位数不左移
  hadc1.Init.OversamplingMode = DISABLE;  //关闭过采样
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  multimode.Mode = ADC_MODE_INDEPENDENT; //独立模式
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }
  sConfig.Channel = ADC_CHANNEL_16;  //通道16
  sConfig.Rank = ADC_REGULAR_RANK_1;  //第 1 个序列
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;  //采样时间
  sConfig.SingleDiff = ADC_SINGLE_ENDED;  //单端输入
  sConfig.OffsetNumber = ADC_OFFSET_NONE;  //不选择偏移序号
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
void MX_ADC3_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};
  hadc3.Instance = ADC3;
  hadc3.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV16;
  hadc3.Init.Resolution = ADC_RESOLUTION_16B;
  hadc3.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc3.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc3.Init.LowPowerAutoWait = DISABLE;
  hadc3.Init.ContinuousConvMode = DISABLE;
  hadc3.Init.NbrOfConversion = 1;
  hadc3.Init.DiscontinuousConvMode = DISABLE;
  hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc3.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
  hadc3.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc3.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
  hadc3.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc3) != HAL_OK)
  {
    Error_Handler();
  }
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

5.ADC读取函数

void sort(unsigned short int a[], int n)
{
    int i, j, t;
 
    //元素从小到大排列
    for (i = 0; i < n - 1; i++) {
        for (j = 0; j < n - i - 1; j++) {
            if (a[j] > a[j + 1]) {
                t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
            }
        }
    }
}
 
int read(int channel)
{
    int i;
    unsigned long int temp = 0;
    unsigned short int data[50];
    ADC_ChannelConfTypeDef channel_config;
    int channel_remap[2] = {ADC_CHANNEL_16,ADC_CHANNEL_1};//ADC1,16通道
 
    channel_config.Channel = channel_remap[channel]; //通道选择
    channel_config.Offset = 0; //偏移量为0
    channel_config.Rank = ADC_REGULAR_RANK_1; //第一个序列
    channel_config.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; //采样时间
channel_config.SingleDiff = ADC_SINGLE_ENDED; //单端输入
     channel_config.OffsetNumber = ADC_OFFSET_NONE;  //不选择偏移序号
 
    for(i = 0;i < 50;i ++){
        if(channel == 0){ //如果通道为0,则使能ADC1
            HAL_ADC_ConfigChannel(&hadc1,&channel_config);
            HAL_ADC_Start(&hadc1);
            while(!__HAL_ADC_GET_FLAG(&hadc1,ADC_FLAG_EOC));
            data[i] = HAL_ADC_GetValue(&hadc1);
        }else if(channel == 1){ //如果通道为1,则使能ADC3
            HAL_ADC_ConfigChannel(&hadc3,&channel_config);
            HAL_ADC_Start(&hadc3);
            while(!__HAL_ADC_GET_FLAG(&hadc3,ADC_FLAG_EOC));
            data[i] = HAL_ADC_GetValue(&hadc3);     
        }
    }
 
    sort(data,50);
    for(i = 20;i < 30;i++){ //取ADC排序后的中间10位数值
        temp += data[i];
    }
 
    temp = temp / 10; //取ADC平均值
    if(channel == 0){ //读取ADC1的值
        my_adc.value[channel] = temp * ADC_REF / 65536;
    }
 
    return temp;
}
 
static int read_mux(void)
{	//打开通道并读取ADC的值
    CHANNEL_0_ON;
    my_adc.value[1] = my_adc.read(1) * ADC_REF / 65536;
 
    CHANNEL_1_ON;
    my_adc.value[2] = my_adc.read(1) * ADC_REF / 65536;
 
    CHANNEL_2_ON;
    my_adc.value[3] = my_adc.read(1) * ADC_REF / 65536;
 
    CHANNEL_3_ON;
    my_adc.value[4] = my_adc.read(1) * ADC_REF / 65536; 
 
    CHANNEL_4_ON;
    my_adc.value[5] = my_adc.read(1) * ADC_REF / 65536; 
 
    CHANNEL_5_ON;
    my_adc.value[6] = my_adc.read(1) * ADC_REF / 65536; 
 
    CHANNEL_6_ON;
    my_adc.value[7] = my_adc.read(1) * ADC_REF / 65536; 
 
    CHANNEL_7_ON;
    my_adc.value[8] = my_adc.read(1) * ADC_REF / 65536; 
 
    return 0;
}

五.实验步骤

  1. 把仿真器与iCore4TX的SWD调试口相连(直接相连或者通过转接器相连);
  2. 把iCore4TX通过Micro USB线与计算机相连,为iCore4TX供电;
  3. 打开 Keil MDK 开发环境,并打开本实验工程;
  4. 烧写程序到 iCore4TX 上;
  5. 也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。

六.实验现象

附录:

1、选择相应的位(32 位或 64 位)双击,NEXT→NEXT→Install→NEXT→Finished。

2、打开 Modbus Poll,点击 Connection connect,输入 SN.txt 中的序列号。 3、按下图进行设置,点击 OK 即可。