之前写的乱七八糟一大堆4


Modbus 协议是应用于 电子控制器上的 一种通用语言。

通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。
它已经成为一通用工业标准。

有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。

此协议定义了一个 控制器能认识使用的 消息结构, 而不管它们是经过 何种网络进行通信的。

现在Modbus已经是工业领域全球最流行的协议。
此协议支持传统的RS-232、RS-422、RS-485和以太网设备。

许多工业设备,包括PLC,DCS,智能仪表等 都在使用Modbus协议 作为他们之间的通讯标准。

Modbus协议包括ASCII、RTU、TCP等,并没有规定物理层。

标准的Modicon控制器使用RS232C实现串行的Modbus。
Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,
数据通讯采用Maser/Slave方式。


串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,
但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。

-------------------------------------------------------------------------------------------------------
它描述了一控制器请求访问其它设备的过程,
以及如何回应来自其它设备的请求,以及怎样侦测错误并记录。

它制定了消息域格局和内容的公共格式。

当在一Modbus网络上通信时,
此协议决定了 每个控制器 须要知道它们的设备地址,
识别 按地址发来的消息,决定要产生何种行动。

如果需要回应,控制器将生成反馈信息 并用Modbus 协议发出。

在其它网络上,包含了 Modbus 协议的消息转换为 在此网络上使用的帧或包结构。
这种转换也扩展了 根据具体的网络 解决 节地址、路由路径及错误检测的方法。

1.频率

/**根据需要的频率,求得单次高低电平的周期,再以50%占空比输出。**/
//设定开关频率 相当于 模拟输出一个pwm波,eg:100hz 50%
?
void led_frequency_ctrl(void)
{      
 period = 1/frequency*1000;  //1
 
 if(CheckDelay((uint32_t*)&time_f)==0)
{
   Set_Delay_Time(period,(uint32_t*)&time_f);  //1
}  
 if(time_f <= period/2)  //2
{
   PAout(1) = 1;
   MP_ModBus1.Hold_Reg[2] = 0;
}
 else
{
   PAout(1) = 0;
   MP_ModBus1.Hold_Reg[2] = 1;
}
}
?
?
/**之后再1ms调用**/
static void Task_1ms(void)
{
 DelayTimeCount_ms((uint32_t*)&time_f); //!!!
 TimeCountReceive(&U_D_Uart2);
}
?

 

 

 

2.modbus主从机主要代码

 

注:主机只需要发什么功率什么频率以及读取已经处理好的监控值,真正控制处理灯的是从机。

例如:

第三功能,需要只在开灯情况下读取监控值,那么就让从机只在开灯状态才存入就好,这样主机获取的都是开灯状态下的值,不需要自己在判断。

再例如:

设定频率,主机只需要告诉从机,你要以多大频率开关灯,从机得知后自主转换控制频率。而不是之前,主机亲自控制每一次开关达到控制频率效果,复杂而且通信量很大出错率也高。

 

/**主机**/
/**实现3/4个功能:1.灯开关频率设定 2.功率设定 3.读取监控值**/
?
?
?
#include "md_data_set.h"
?
struct all_ctrl  struct_ctrl;
struct pid_para  pid_dev;
uint16_t v_detection = 0;
uint16_t p_monitor = 0;
uint16_t led_on_off_time = 0;
uint16_t setp_time = 0;
?
?
void power_ctrl_init(void)
{
 struct_ctrl.powerset = &power_ctrl;
 struct_ctrl.set_power_ok = 0;
}
?
/*************************************************************************/
void led_on_off_poll(void)
{
 static uint8_t x = 0;
 switch(x)
{
   case 0:
  {
     if(CheckDelay(&led_on_off_time)==0)
    {
       SetLedEnable();
       Set_Delay_Time(300,&led_on_off_time);
       x++;        
    }      
  }break;
   case 1:
  {
     if(struct_ctrl.powerset->LEDUpdate == 1)
    {
       struct_ctrl.powerset->LEDUpdate = 0;
    }
     x = 0;
  }break;
   default:
  {
      x = 0;
  }break;
}
}
/*************************************************************************/
void power_set_poll(void)
{
 static uint8_t State = 0;
 switch(State)
{
   case 0:
  {
     if(CheckDelay(&setp_time)==0)
    {
       SetPowerEnable();
       struct_ctrl.set_power_ok = 0;
       Set_Delay_Time(1100,&setp_time);
       State++;        
    }      
  }break;
   case 1:
  {
     if(struct_ctrl.powerset->DataUpdate == 1)
    {
       struct_ctrl.powerset->DataUpdate = 0;
       struct_ctrl.set_power_ok = 1;      
       
    }  
     State = 0;
  }break;
   default:
  {
      State = 0;
  }break;
}
}
/*************************************************************************/
void read_poll(void)
{
 static uint8_t State = 0;
 switch(State)
{
   case 0:
  {
      ReadPowerEnable();
      State++;
  }break;
   case 1:
  {
     if(struct_ctrl.powerset->PowerDataUpdate == 1)
    {
        struct_ctrl.powerset->PowerDataUpdate = 0;
        v_detection = struct_ctrl.powerset->MMD.Hold_Reg[3];
        p_monitor = struct_ctrl.powerset->MMD.Hold_Reg[4];        
    }  
     State = 0;
  }break;
   default:
  {
      State = 0;
  }break;
}
}
/*************************************************************************/
void commit_scope_poll(void)
{
   rev_string_fuction();    
   if(uart_3.flag_v == 1)
  {
     McuSendData(UartVisSco, (int16_t *)&struct_ctrl.powerset->MMD.Hold_Reg[3]);
  }
   if(uart_3.flag_p == 1)
  {
     McuSendData(UartVisSco, (int16_t *)&struct_ctrl.powerset->MMD.Hold_Reg[4]);
  }
}
?
?
?
?

 

/**从机主要代码**/
?
?
#include "data_trans.h"
?
uint16_t PowerSet = 0;  //modbus master set
uint16_t PowerOut = 0;  //pwm
uint16_t time_f = 0;
float frequency = 0; //led on/off
?
double v_detection = 0.00f;
double power_monitor = 0.00f;
float  period = 0;
?
void registers_init(void)
{
 time_f = 0;
 PAout(1) = 0;
 frequency = 0;
 MP_ModBus1.Hold_Reg[0] = 0; //hz
 MP_ModBus1.Hold_Reg[1] = 0; //powerout
///MP_ModBus1.Hold_Reg[2] = 0; //on is 1,off is 0 //这块没必要,是错误的想法,详见上面解析
 MP_ModBus1.Hold_Reg[3] = 0; //v_m
 MP_ModBus1.Hold_Reg[4] = 0; //p_m
}
?
/******************************************************/
void get_v_detection(void)
{
 uint16_t temp_0;
 float v0;
 
 temp_0 = ADC_ConvertedValue[0];
 v_detection = (float)temp_0/4096*3.3;
 v0 = v_detection*1000;
 MP_ModBus1.Hold_Reg[3] = (uint16_t)v0;
}
void get_power_monitor(void)
{
 uint16_t temp_1;
 float p1;
 
 temp_1 = ADC_ConvertedValue[1];
 power_monitor = (float)temp_1/4096*3.3;
 p1 = power_monitor*1000;
 MP_ModBus1.Hold_Reg[4] = (uint16_t)p1;
}
/******************************************************/
void led_frequency_ctrl(void)
{      
 period = 1/frequency*1000;
 
 if(CheckDelay((uint32_t*)&time_f)==0)
{
   Set_Delay_Time(period,(uint32_t*)&time_f);  
}  
 if(time_f <= period/2)
{
   PAout(1) = 1;
   MP_ModBus1.Hold_Reg[2] = 0;
}
 else
{
   PAout(1) = 0;
   MP_ModBus1.Hold_Reg[2] = 1;
}
}
/******************************************************/
void SetPowerOut(uint16_t Pset)
{
 __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_4,Pset);
}
/******************************************************/
void modbus_slave_poll(void)
{
 static uint8_t State = 0;
 switch(State)
{
     case 0:
    {
        frequency = MP_ModBus1.Hold_Reg[0];
        State++;
    }break;
     case 1:
    {  
         PowerSet = MP_ModBus1.Hold_Reg[1];
         SetPowerOut(PowerSet);
         State++;
    }break;
     case 2:
    {
       State = 0;
    }break;
     
     default:
    {
        State = 0;
    };
}
}
?
/******************************************************/
/******************************************************/
/******************************************************/
static void Task_1ms(void)
{
  DelayTimeCount_ms((uint32_t*)&time_f);
  TimeCountReceive(&U_D_Uart2);
}
static void Task_2ms(void)
{
 if(MP_ModBus1.Hold_Reg[2]==1)  //处理led开时候读取监控数据
{
   get_v_detection();
   get_power_monitor();
}
}
static void Task_5ms(void)
{

}
static void Task_10ms(void)
{
modbus_slave_poll();    //这块要注意!!不能放在大循环,因为会导致没有时间响应主机命令!!
}

 

 

关于RTT初始化

一.

  1. “board init functions” 为所有通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数。

  2. “pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn)申明的初始化函数。

  3. “device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。

  4. “components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn)申明的初始化函数。

  5. “enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。

  6. “application init functions” 为所有通过 INIT_APP_EXPORT(fn)申明的初始化函数。

 

二.

用来实现自动初始化功能的宏接口定义详细描述如下表所示:

初始化顺序宏接口描述
1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动
2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数
3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备
4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP
5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统
6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用

 

三.

系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。内核对象分为两类:静态内核对象和动态内核对象.

静态内核对象:通常放在 RW 段和 ZI 段中,在系统启动后 在程序中初始化.

动态内核对象:从内存堆中创建的,而后 手工做初始化.


 

 

 

 

 

 

乱七八糟一些摘抄


ISP:

用写入器将code烧入,不过,芯片可以在目标板上,不用取出来,在设计目标板的时候就将接口设计在上面,所以叫"在系统编程",即不用脱离系统;

IAP:

在应用编程,有芯片本身(或通过外围的芯片)可以通过一系列操作将code写入,比如一款支持Iap的单片机,内分3个程序区,1作引导程序区,2作运行程序区,3作下载区,芯片通过串口接收到下载命令,进入引导区运行引导程序,在引导程序下将new code内容下载到下载区,下载完毕并校验通过后再将下载区内容复制到2区,运行复位程序,则Iap完成;

应用场合:

1,ISP 程序升级需要到现场解决,不过好一点的是不必拆机器了; 2,IAP 如果有网管系统的话,用网管下载一切搞定,人不用跑来跑去, 这可能是他们的优点或应用吧

 

应用示例解释

1.0 通常在用户需要实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码。

1.1 第一个项目程序不执行正常的功能操作,而只是通过某种通信管道(如USB、USART)接收程序或数据,执行对第二部分代码的更新;

1.2 第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:

1)检查是否需要对第二部分代码进行更新 2)如果不需要更新则转到 4) 3)执行更新操作 4)跳转到第二部分代码执行

1.3 第一部分代码必须通过其它手段,如JTAG或ISP烧入;

1.4 第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一道烧入,以后需要程序更新是再通过第一部分IAP代码更新。

 

硬件定时器外部输入计数

采用了两种方法处理:

一种是采用另一个定时器实时的检测溢出标志及方向标志,这种方法是可行的。

即使速度很快的时候依然计数准确。但是出现了新的问题,用作定时器的时钟,当定时周期设置的很小的时候才可以计数准确,但是由于中断的优先级高,导致无法进入主循环程序。

附上检测代码。这样的处理方法,在速度慢的时候可以检测正确。但是定时周期要设置足够小。CR1的bit4代表计数方向标志,1代表减二代表加;SR的bit0代表溢出中断标志,1代表溢出。该位是硬件置1软件清零。

另一种是使用编码器溢出中断。中断的时候对编码器溢出方向进行判断,然后根据方向进行累加。

 

最后,更简单的检测编码器溢出中断并进行累计的方法。

其实当编码器的定时器溢出的时候,会导致进入中断。这样直接用编码器的中断函数即可。但是需要注意的是,需要在编码器的定时器配置的时候,配置中断。如果不配置,就会导致程序一运行就死机。一直停在DMA2_Channel4_5_IRQHandler这一句。

//附上最终测试验证完成的代码 编码器初始化配置 (注:如果不适用中断溢出的方法,无需配置这部分)
NVIC_InitStructure.NVIC_IRQChannel =TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

 

?
?
并且我适用的是tim3重定义到pc7 pc6的。也可以使用部分重映射和默认引脚。
?
void EncoderTimInit()
{
   //   对TIM_ICInitStructure的配置和对TIM_EncoderInterfaceConfig函数的使用实际上是冲突的,两个语句中的极性的设置是重复的
?
   GPIO_InitTypeDefGPIO_InitStructure;
   TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
   TIM_ICInitTypeDef TIM_ICInitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;
?
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3时钟
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//使能GPIOC时钟
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);  //AFIO时钟
?
   GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);     //TIM选择全复用功能使能
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;//PC6 PC7浮空输入
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//GPIO_Mode_IPU GPIO_Mode_IN_FLOATING
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_Init(GPIOC,&GPIO_InitStructure);
?
   TIM_DeInit(TIM3);    //TIM3初始化
?
   /* Timer configuration in Encoder mode */
   TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频
   TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM3_PERIOD-1; //计数40000 定时 T = 1/72000000 *40000 S= 1/1.8ms
   TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //设置时钟分频系数:不分频
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
   TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
?
   //编码配置   编码模式  
   TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //TIM_ICPolarity_Rising上升沿捕获
   TIM_ICStructInit(&TIM_ICInitStructure);    //输入捕获模式配置为硬件默认值
   TIM_ICInitStructure.TIM_ICFilter = 0x06;   //ICx_FILTER; //比较滤波器
   TIM_ICInit(TIM3, &TIM_ICInitStructure);    //调用库函数把配置信息填充进去
?
   NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);
?
   //TIM_ARRPreloadConfig(TIM3,ENABLE);
   //TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Disable);
?
   //TIM_SetAutoreload(TIM3,0xffff);   //Clear all pending interrupts
   TIM_ClearFlag(TIM3,TIM_FLAG_Update);//清除TIM3的更新标志位
   TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//使能中断                                                  
   //Reset counter 计数初始值
   TIM3->CNT = 0;
   TIM_Cmd(TIM3, ENABLE);
}

 

 


 

部分RT-Thread概述

系统 
版本 BSP
rt-thread-master(4.0.3) stm32l475-atk-pandora(MDK-AC5)
  stm32f407-st-discovery(MDK-AC6.14)
编译环境 MDK5
   
内核部分  
信号量 使用信号量来实现线程同步
互斥量 使用互斥锁来实现资源的单一访问
事件集 对多线程同步工作
线程 多个线程实现多个任务的伪同时进行
定时器 实现延时动作处理,软件RTC模拟
内存管理 动态内存管理
   
内核组件  
控制台 打印调试信息,和串口升级
文件系统 SD卡读写
设备驱动框架 底层硬件驱动对接
POSIX 系统统一接口,供应用调用
NET 网卡相关接口管理
Ymodem 升级
   
软件包  
webclient 获取天气数据
cJSON 云平台通信的数据交互
netutils 网络获取时间
AT DEVICE ESP8266的底层通信
ota_dowmloader 串口升级
EasyFlash 设备参数信息存储
FAL OTA升级
sensor drivers 温度传感器驱动
FlexibleButton UI的按键控制

 


有关堆栈

在搭载实时操作系统内核的嵌入式软件中,栈往往分为两大类,除了满足系统基本的主栈(main stack)外,往往还需要进程/线程栈(process stack)。这两部分内存空间是独立存在的:主栈位于系统的栈区(stack),而线程堆栈往往定义在堆区(heap)或静态区(static),理解这一点,是理解 MCU 堆栈的关键前提。

上电后,系统仅初始化了 MSP,需要通过额外的汇编代码建立完整的双堆栈系统。当实时内核准备就绪,线程调度正常运行,双堆栈机制开始工作。进中断时系统根据当前状态自动切换堆栈,进程上下文切换时会更新不同线程的 PSP,通过修改 EXC_RETURN可以手动切换 MSP/PSP

双堆栈机制使得内核/ISR 堆栈和线程应用堆栈分开管理,通过不同的堆栈指针寄存器完成切换,大大提高了系统的效率,在绝大部分的嵌入式实时操作系统中,都使用了双堆栈机制,如 ucos、FreeRTOS、RT-Thread 等。

PS:在一些简单的应用中,例如裸机程序,可以从头到尾都只使用主堆栈,只要确保分配足够的空间即可。*