【沁恒RISC-V内核 CH582】ADC与通用控制器的应用
[复制链接]
这一期带来一些非常实用的干货。在实际控制系统设计过程中,我们通常会使用到设计的滤波器,通过MATLAB我们除了得到IIR和FIR型的滤波器还可以通过z域的传递函数实现。本次我将会用自己写的一个离散控制器结合一个最基础的版本滤波器(三项均值平滑)来实现这个板子的ADC采样。
因为实在没有特别多的时间调试滤波器,所以只能取这个简单的替代方案。如果大家有兴趣,对于直接导入MATLAB生成的IIR和FIR的可移植控制器可以关注下面这个github仓库,后续的代码维护都将在这个仓库完成。
由于目前这个仓库还没有开始整理,等我得空就开始整理。下面直接给出这个控制器的代码:
头文件:z_transfer_function.h
/**
* @file z_trnasfer_function.h
* @author Javnson
* @email javnson@zju.edu.cn
* @date 2022.03.27(Create:2022.03.25)
* @version 1.0.0:0000
* @licence Apache License, Version 2.0
*
* @brief *
* Change history :
* <Date> | <version> | <author> | <Description> |
* 2022.03.25 | 1.0.0:0000 | Javnson | Create File |
* 2022.03.27 | 1.0.1:0002 | Javnson | Complete basic z function |
* 2022.03.28 | 1.0.2:0003 | Javnson | init_z_function_struct_mm and deinit_z_function_struct_mm |
*
*/
/* Application Notes
* Application Note: 3/27/2022
* when you use BUFFER_POINTER_ENDLESS mode to ask the `fn_clac` routine to move pointer. you must pay
* attention to the origin for the array. The first `cm_num_order` items of `p_input_buffer` are occupied
* due to calculation needs.so as `cm_den_order` items of `p_output_buffer`.
* You can also give the Z transfer function the initial condition after init routine, by set the
* `z_function::p_input_buffer` and `z_function::p_output_buffer`.
*
* Application Note: 3/28/2022
* Please notice that the order of the denominator and numerator must be carefully confirm!
* If you don't set the correct coefficient or order, the result may be divergent or shaking.
*/
/* Demo routine
* Demo 1 Trinomial mean filter
* transfer function: $T(z)=\frac{1+z^{-1}+z^{-2}}{3}$
discrete_control_system dcs;
z_function z1;
dcs.m_discrete_time = 0.001; // 10 kHz
dcs.m_zero = 0.0f;
z1.cm_num_order = 2;
z1.cm_den_order = 0;
float num[3] = { 1.0f, 1.0f, 1.0f };
float den[1] = { 3.0f };
z1.cm_num_param = num;
z1.cm_den_param = den;
float* input_array = new float[2 << 13];
float* output_array = new float[2 << 13];
z1.p_input_buffer = input_array;
z1.p_output_buffer = output_array;
z1.m_buffer_state = z1.BUFFER_POINTER_ENDLESS;
init_z_function_struct(&z1, &dcs);
for (size_t i = 0; i < 10000; i++)
{
z1.m_input = i;
z1.fn_calc(&z1);
} // set break point here.
* You may notice the mean filter's result at the break point.
*/
#include <discrete_control_system.h>
#ifndef _FILE_Z_TRNASFER_FUNCTION_H_
#define _FILE_Z_TRANSFER_FUCNTION_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief This object implements a physically realizable z-domain function.
* You may call the fn_calc to calculate the result for the Z domain.
* In general you have to design the Z function firstly, and simplify the equation to the following basic form.
$$
\begin{aligned}
H(z) &= \frac{R(z)}{X(z)}=\frac{a_0+\Sigma_{p=1}^{m}{a_pz^{-p}}}{1+\Sigma_{q=1}^n{b_qz^{-q}}} \\
r_n &= a_0x_n+\Sigma_{p=1}^{m}{a_px_{n-p}}-\Sigma_{q=1}^n{b_qr_{n-q}}
\end{aligned}
$$
* You may read it by LaTex compiler, if necessary.
* Than, based on the equation, the user must pass two vectors as the numerator
* and the denominator.
* the entity provide a calculate function, named @fn_calc user may call the function, at the right time.
*
*/
typedef struct _entity_z_transfer_function
{
/**
* @brief This function specify the calculate function.
* You may provide the input param and call the function to get the result.
*
* @param obj: input the Z-domain function object.
*
* @retval None
*
*/
void(*fn_calc)(struct _entity_z_transfer_function* obj);
dcs_param_t m_input;
dcs_param_t m_output;
dcs_difference_t cm_num_order; // const member numerator order, refer to p, this value may be set to 0, if none history information are needed.
dcs_difference_t cm_den_order; // const member denominator order, refer to q, this value may be set to 0, if none history information are needed.
// these params are stored in decreasing order
dcs_param_t* cm_num_param; // const member numerator
// The pointer point to an array that contain more than `cm_num_order + 1` items, as the numerator param.
// In general, the first param should be 1, in order to match the equation.
dcs_param_t* cm_den_param; // const member denominator
// The pointer point to an array that contain more than `cm_den_order + 1` items.
dcs_param_t* p_input_buffer; // The buffer must point to an array that contain more than `cm_num_order` items,
// and only cm_num_order element will be used.
dcs_param_t* p_output_buffer; // The buffer must point to an array that contain more than `cm_den_order` items,
// and only cm_den_order element will be used.
//
// An example for 3-order system fixed system mode or endless system mode.
// index : [-1] [0] [1] [2]
// x_(-4) x_(-3) x_(-2) x_(-1)
// r_(-4) r_(-3) r_(-2) r_(-1)
// warning: the run function won't check the boundary conditions. User must ensure the boundary condition is reasonable.
// An example for 3-order system circle mode. Every oldest data will be replaced by the newest data.
// index : [0] [1] [2] [3]
// 1st x_(-4) x_(-3) x_(-2) x_(-1)
// r_(-4) r_(-3) r_(-2) r_(-1)
// index : [0] [1] [2] [3]
// 2nd x_(0) x_(-3) x_(-2) x_(-1)
// r_(0) r_(-3) r_(-2) r_(-1)
// index : [0] [1] [2] [3]
// 3rd x_(0) x_(1) x_(-2) x_(-1)
// r_(0) r_(1) r_(-2) r_(-1)
enum {
BUFFER_POINTER_FIXED = 1, // The buffer label is fixed, buffer[0] is the last item(previous item for output),
// buffer[1] is the previous item for buffer[0].
// That is, a circular copy is executed every time calculation (@fn_calc) is completed.
BUFFER_POINTER_CIRCLE, // The calculation results will be stored in the buffer in a circular manner.
// The last input param will replace the earliest stored data.
// And the function will automatically match the label and index.
BUFFER_POINTER_ENDLESS // The calculation result will stored in an endless array.
// The pointer will move to next item every time calculation is completed,
// and user must ensure the buffer has enough length, or move the pointer address manually.
} m_buffer_state;
dcs_difference_t p_cir_pos; // This variable record the position of the oldest item.
struct _entity_discrete_control_system *m_dcs;
}z_function, * pz_function;
/**
* @brief init the z transfer function entity struct.
*
* @param obj: the struct to be init.
* dcs: the discrete control system the z_function module based on.
*
* @retval None
* @note The function mast be call after all pointer was set.
*
*/
void init_z_function_struct(struct _entity_z_transfer_function* obj, struct _entity_discrete_control_system* dcs);
/**
* @brief init z transfer function entity struct and alloc memory automatically.
*
* @param obj: the struct to be init.
* dcs: the discrete control system the z_function module based on.
*
* @retval None
* @note This function will call @malloc. you must ensure the function is enable in your production environment.
* Otherwise, you have no choice to use @init_z_function_struct and alloc memory manually.
* This function will only allocate memory for p_input_buffer and p_output_buffer in minimum size.
* @warning The time you use this function to construct a z_function entity. You need to call
*/
void init_z_function_struct_mm(struct _entity_z_transfer_function* obj, struct _entity_discrete_control_system* dcs);
/**
* @brief init z transfer function entity struct and alloc memory automatically.
*
* @param obj: the struct to be init.
* dcs: the discrete control system the z_function module based on.
*
* @retval None
* @note This function will call @malloc. you must ensure the function is enable in your production environment.
* Otherwise, you have no choice to use @init_z_function_struct and alloc memory manually.
* This function will only allocate memory for p_input_buffer and p_output_buffer in minimum size.
* @warning The time you use this function to construct a z_function entity. You need to call
*/
void deinit_z_function_struct_mm(struct _entity_z_transfer_function* obj);
/**
* @brief This function is default function for the z transfer.
*
* @param obj: the z_transfer_function object.
*
* @retval None
*
*/
void _z_func_calc_default(struct _entity_z_transfer_function* obj);
#ifdef __cplusplus
}
#endif
#endif
源文件:z_transfer_function.c
/**
* @file z_trnasfer_function.c
* @author Javnson
* @email javnson@zju.edu.cn
* @date 2022.03.27(Create:2022.03.26)
* @version 1.0.0:0000
* @licence Apache License, Version 2.0
*
* @brief
*
* Change history :
* <Date> | <version> | <author> | <Description> |
* 2022.03.26 | 1.0.0:0000 | Javnson | Create File |
* 2022.03.27 | 1.0.1:0002 | Javnson | Complete basic z function |
*
*/
#include <discrete_control_system.h>
#include <z_transfer_function.h>
#include <memory.h>
void _z_func_calc_default(pz_function obj)
{
cs_assert_error(obj->cm_num_param, "num_param must be a array and not point to nullptr");
cs_assert_error(obj->cm_den_param, "den_param must be a array and not point to nullptr");
cs_assert_error(obj->cm_den_param[0] != (dcs_param_t)0, "den_param[0] must be an nonzero value.");
dcs_param_t r_n = obj->cm_num_param[0] * obj->m_input;
// this switch ensure the judgment will not happen usually.
switch (obj->m_buffer_state)
{
case BUFFER_POINTER_FIXED:
for (dcs_difference_t i = 1; i <= obj->cm_num_order; ++i)
r_n += *(obj->cm_num_param + i) * (*(obj->p_input_buffer + obj->cm_num_order - i));
for (dcs_difference_t i = 1; i <= obj->cm_den_order; ++i)
r_n -= *(obj->cm_den_param + i) * (*(obj->p_output_buffer + obj->cm_den_order - i));
// get the result.
obj->m_output = r_n / obj->cm_den_param[0];
// memory move.
for (dcs_difference_t i = 1; i < obj->cm_num_order; ++i)
obj->p_input_buffer[i - 1] = obj->p_input_buffer;
if (obj->cm_num_order) obj->p_input_buffer[obj->cm_num_order - 1] = obj->m_input;
for (dcs_difference_t i = 1; i < obj->cm_den_order; ++i)
obj->p_output_buffer[i - 1] = obj->p_output_buffer;
if (obj->cm_den_order) obj->p_output_buffer[obj->cm_den_order - 1] = obj->m_output;
return;
case BUFFER_POINTER_CIRCLE:
for (dcs_difference_t i = 1; i <= obj->cm_num_order; ++i)
r_n += *(obj->cm_num_param + i) * (*(obj->p_input_buffer + (obj->cm_num_order - i + obj->p_cir_pos) % obj->cm_num_order));
for (dcs_difference_t i = 1; i <= obj->cm_den_order; ++i)
r_n -= *(obj->cm_den_param + i) * (*(obj->p_output_buffer + (obj->cm_den_order - i + obj->p_cir_pos) % obj->cm_den_order));
// get the result.
obj->m_output = r_n / obj->cm_den_param[0];
// replace the oldest data
if (obj->cm_num_order) obj->p_input_buffer[obj->p_cir_pos % obj->cm_num_order] = obj->m_input;
if (obj->cm_den_order) obj->p_output_buffer[obj->p_cir_pos % obj->cm_den_order] = obj->m_output;
// Keep good circulation
if (++obj->p_cir_pos == obj->cm_den_order * obj->cm_num_order)
obj->p_cir_pos = 0;
return;
case BUFFER_POINTER_ENDLESS:
for (dcs_difference_t i = 1; i <= obj->cm_num_order; ++i)
r_n += *(obj->cm_num_param + i) * (*(obj->p_input_buffer + obj->cm_num_order - i));
for (dcs_difference_t i = 1; i <= obj->cm_den_order; ++i)
r_n -= *(obj->cm_den_param + i) * (*(obj->p_output_buffer + obj->cm_den_order - i));
// get the result.
obj->m_output = r_n / obj->cm_den_param[0];
// Move to net position.
*(++obj->p_input_buffer + obj->cm_num_order - 1) = obj->m_input;
*(++obj->p_output_buffer + obj->cm_den_order - 1) = obj->m_output;
return;
default:
cs_assert_error(0, "Unknown buffer style.");
}
return;
}
void init_z_function_struct(struct _entity_z_transfer_function* obj, struct _entity_discrete_control_system* dcs)
{
cs_assert_error(obj, "You must pass an obj pointer to the z_function struct.\n");
cs_assert_error(obj->cm_den_param, "You must specify the denominator param firstly.\n");
cs_assert_error(obj->cm_num_param, "You must specify the numerator param firstly.\n");
cs_assert_error(obj->cm_num_order == 0 || obj->p_input_buffer, "You must specify the input buffer.\n");
cs_assert_error(obj->cm_den_order == 0 || obj->p_output_buffer, "You must specifu the output buffer.\n");
cs_assert_error(dcs, "The z_function module is based on discrete control system, you must define and pass a dcs object,\n");
obj->m_dcs = dcs;
for (dcs_difference_t i = 0; i < obj->cm_num_order; ++i)
obj->p_input_buffer = obj->m_dcs->m_zero;
for (dcs_difference_t i = 0; i < obj->cm_den_order; ++i)
obj->p_output_buffer = obj->m_dcs->m_zero;
obj->fn_calc = _z_func_calc_default;
obj->p_cir_pos = 0;
}
void init_z_function_struct_mm(struct _entity_z_transfer_function* obj, struct _entity_discrete_control_system* dcs)
{
obj->p_input_buffer = obj->cm_num_order == 0 ? 0 : (dcs_param_t*)malloc(sizeof(dcs_param_t) * obj->cm_num_order);
obj->p_output_buffer = obj->cm_den_order == 0 ? 0 : (dcs_param_t*)malloc(sizeof(dcs_param_t) * obj->cm_den_order);
init_z_function_struct(obj, dcs);
}
void deinit_z_function_struct_mm(struct _entity_z_transfer_function* obj)
{
if (obj->cm_num_order != 0) free(obj->p_input_buffer);
if (obj->cm_den_order != 0) free(obj->p_output_buffer);
}
首先简介一下这套控制器。
这一套控制器本着对标MATLAB的原则进行设计,旨在对于一个已知的MATLAB控制框图,可以直接基于这一套控制系统在嵌入式系统上进行复刻部署。尽管MATLAB有相当丰富的代码生成功能,但是对于一些新兴芯片支持并没有特别友好。另外对于想在完成控制原理设计后进行实物设计的情况并很方便,另外做一波自己的储备(有过科研经历的同学可能明白)。
这套控制系统基于一个discrete_control_system对象(结构体)进行拓展设计,组合成为一套完成的控制系统。目前摘录出来的只是实现Z控制器的部分。
对于Z控制器到底是什么,就是实现了一个下面的Z域传递函数计算器。至于更加细节的内容不是此处讨论的重点,不做过多赘述
$$
\begin{aligned} H(z) &= \frac{R(z)}{X(z)}=\frac{a_0+\Sigma_{p=1}^{m}{a_pz^{-p}}}{1+\Sigma_{q=1}^n{b_qz^{-q}}} \\ r_n &= a_0x_n+\Sigma_{p=1}^{m}{a_px_{n-p}}-\Sigma_{q=1}^n{b_qr_{n-q}} \end{aligned}
$$
接下来我们需要设置一个10kHz的Timer,在中断中完成采样,并执行一个示例传递函数,翻译成人话就是每连续三项取平均
$$
H(z)=\frac{1+z^{-1}+z^{-2}}{3}
$$
作为第三位体验人CH582,下面是移植过程。
首先,我们需要一个控制系统的主时钟来主导运行过程,这个时钟频率应当设置为f=10kHz,我们在本案例中使用timer0 来实现。系统时钟为6.4\,MHz那么我们应当设置分频系数为6400。
接下来需要将控制系统的代码下载下来,并拷贝到src 文件夹中。首先需要设置include文件夹。可以在项目处右键,选择属性,进入C/C++ General/Paths and Symbols ,选项卡中,选择右侧第一项includes,在Language中选择GNU C,在其中添加:/${PWD}/../src/inc 和/${PWD}/../src/inc/control_suite 。
在确定之后会要求我们重新编译。
接着我们在Main.c 文件中添加#include <discrete_control_system.h> 和#include <z_transfer_function.h> 。
接下来首先需要设置合适的数据类型以适应系统。
需要在文件emlib_setting.h 中添加#define DCS_PARAM_USE_INT16 和#define DCS_UNIT_USE_UIINT16 ,以及#define DCS_DIFFERENCE_USE_UINT32 。
以上三个宏将定义使用16位的整数参数,并且内存的位数为32位。
在主函数中完成控制系统的参数初始化:
// 初始化控制系统
dcs.m_discrete_time = 1; // 10 kHz
dcs.m_zero = 0;
z1.cm_num_order = 2;
z1.cm_den_order = 0;
int16_t num[3] = { 1, 1, 1 };
int16_t den[1] = { 3 };
z1.cm_num_param = num;
z1.cm_den_param = den;
z1.p_input_buffer = input_array;
z1.p_output_buffer = output_array;
z1.m_buffer_state = BUFFER_POINTER_FIXED;
init_z_function_struct(&z1, &dcs);
最后添加启动TIM0作为系统时钟。
// 启动TIM0作为系统时钟
TMR0_TimerInit(6400);
TMR0_ITCfg( ENABLE, TMR0_3_IT_CYC_END );
通过编译之后记录如下:
In file included from ../src/Main.c:11:
E:\Hardware\CH583\EVT\EXAM\ADC_blur\src/inc/control_suite/discrete_control_system.h:73:9: note: #pragma message: Now the dcs_unit_t type is default type, which is float type. You may use macro "DCS_UNIT_USE_MANUAL" to specify the unit type clearly.
#pragma message ("Now the dcs_unit_t type is default type, which is float type. You may use macro \"DCS_UNIT_USE_MANUAL\" to specify the unit type clearly.\n")
^~~~~~~
Memory region Used Size Region Size %age Used
FLASH: 8620 B 448 KB 1.88%
RAM: 2240 B 32 KB 6.84%
text data bss dec hex filename
8508 112 356 8976 2310 ADC.elf
通过实物调试可以观察到系统正常执行。
有兴趣的朋友可以试试使用MATLAB生成一个uint16_t 类型的FIR滤波器,可以实现对于ADC的输入滤波。
附:主文件源代码
/********************************** (C) COPYRIGHT *******************************
* File Name : Main.c
* Author : WCH
* Version : V1.0
* Description : adc采样滤波示例
*******************************************************************************/
#include "CH58x_common.h"
#include "CH58x_timer.h"
#include <discrete_control_system.h>
#include <z_transfer_function.h>
signed short RoughCalib_Value = 0; // ADC粗调偏差值
// 初始化控制系统
discrete_control_system dcs;
z_function z1;
int16_t input_array[2];
int16_t *output_array = 0;
void DebugInit( void )
{
GPIOA_SetBits( GPIO_Pin_9 );
GPIOA_ModeCfg( GPIO_Pin_8, GPIO_ModeIN_PU );
GPIOA_ModeCfg( GPIO_Pin_9, GPIO_ModeOut_PP_5mA );
UART1_DefInit();
}
int main()
{
UINT8 i;
SetSysClock( CLK_SOURCE_PLL_60MHz );
// 配置串口调试
DebugInit();
PRINT( "Start @ChipID=%02X\n", R8_CHIP_ID );
// 单通道采样:选择adc通道0做采样,对应 PA4引脚, 首先进行数据校准功能
PRINT( "\n2.Single channel sampling...\n" );
GPIOA_ModeCfg( GPIO_Pin_4, GPIO_ModeIN_Floating );
ADC_ExtSingleChSampInit( SampleFreq_3_2, ADC_PGA_0 );
GPIOA_ModeCfg( GPIO_Pin_5, GPIO_ModeIN_Floating );
RoughCalib_Value = ADC_DataCalib_Rough(); // 用于计算ADC内部偏差,记录到全局变量 RoughCalib_Value中
// 初始化控制系统
dcs.m_discrete_time = 1; // 10 kHz
dcs.m_zero = 0;
z1.cm_num_order = 2;
z1.cm_den_order = 0;
int16_t num[3] = { 1, 1, 1 };
int16_t den[1] = { 3 };
z1.cm_num_param = num;
z1.cm_den_param = den;
z1.p_input_buffer = input_array;
z1.p_output_buffer = output_array;
z1.m_buffer_state = BUFFER_POINTER_FIXED;
init_z_function_struct(&z1, &dcs);
// 启动TIM0作为系统时钟
TMR0_TimerInit(6400);
TMR0_ITCfg( ENABLE, TMR0_3_IT_CYC_END );
while(1);
}
__INTERRUPT
__HIGH_CODE
void TMR0_IRQHandler( void ) // TMR0 定时中断
{
UINT16 data = ADC_ExcutSingleConver() + RoughCalib_Value;
z1.m_input = data;
z1.fn_calc(&z1);
if ( TMR0_GetITFlag( TMR0_3_IT_CYC_END ) )
{
TMR0_ClearITFlag( TMR0_3_IT_CYC_END ); // 清除中断标志
// GPIOB_InverseBits( GPIO_Pin_15 );
}
}
|