[Qinheng RISC-V core CH582] Application of ADC and general controller
[Copy link]
This issue brings some very practical dry goods. In the actual control system design process, we usually use the designed filter. Through MATLAB, we can not only get IIR and FIR filters, but also realize them through the transfer function in the z domain. This time I will use a discrete controller I wrote combined with a most basic version of the filter (three-term mean smoothing) to realize the ADC sampling of this board.
Since I don't have much time to debug the filter, I can only use this simple alternative. If you are interested, you can pay attention to the following github repository for portable controllers of IIR and FIR generated by directly importing MATLAB. Subsequent code maintenance will be completed in this repository.
Since the warehouse has not been sorted yet, I will start sorting it out when I have time. Here is the code of this controller:
Header file: 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 confirmed!
* 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_{np}}-\Sigma_{q=1}^n{b_qr_{nq}}
\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 contains 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 contains 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 be 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 records 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
Source file: 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 an array and not point to nullptr");
cs_assert_error(obj->cm_den_param, "den_param must be an 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 ensures 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);
}
First, let me introduce this controller.
This controller is designed based on the principle of benchmarking MATLAB, aiming to replicate and deploy a known MATLAB control block diagram directly on an embedded system based on this control system. Although MATLAB has quite rich code generation functions, it is not particularly friendly to some emerging chip support. In addition, it is not very convenient for those who want to carry out physical design after completing the control principle design, and also make their own reserves (students with scientific research experience may understand).
This control system is based on a discrete_control_system object (structure) for expansion and design, combined into a complete control system. The excerpt is only the part that implements the Z controller.
What exactly is a Z controller? It implements a Z-domain transfer function calculator as shown below. As for the more detailed content, it is not the focus of this discussion, so I will not go into too much detail.
$$
\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_{np}}-\Sigma_{ q=1}^n{b_qr_{nq}} \end{aligned}
$$
Next we need to set up a 10kHz Timer, complete the sampling in the interrupt, and execute a sample transfer function, which translates to taking the average of three consecutive items.
$$
H(z)=\frac{1+z^{-1}+z^{-2}}{3}
$$
As the third experiencer CH582, here is the transplant process.
First, we need a control system master clock to dominate the operation process. The clock frequency should be set to f=10kHz, which we use in this case timer0 . The system clock is 6.4\,MHz, so we should set the division factor to 6400.
Next, you need to download the control system code and copy it to src a folder. First, you need to set the include folder. You can right-click the project, select Properties, enter C/C++ General/Paths and Symbols the tab, select the first item on the right, includes, select GNU C in Language, and add: /${PWD}/../src/inc and /${PWD}/../src/inc/control_suite .
After confirmation, we will be asked to recompile.
Next we add and Main.c to the file .#include <discrete_control_system.h> #include <z_transfer_function.h>
Next, you first need to set the appropriate data type to suit the system.
Need to add and emlib_setting.h to #define DCS_PARAM_USE_INT16 the file .#define DCS_UNIT_USE_UIINT16 #define DCS_DIFFERENCE_USE_UINT32
The above three macros will define the use of 16-bit integer parameters and the number of bits of memory is 32 bits.
Complete the parameter initialization of the control system in the main function:
// Initialize the control system
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);
Finally, add and start TIM0 as the system clock.
// Start TIM0 as system clock
TMR0_TimerInit(6400);
TMR0_ITCfg( ENABLE, TMR0_3_IT_CYC_END );
After compilation, the records are as follows:
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: 2240B 32KB 6.84%
text data bss dec hex filename
8508 112 356 8976 2310 ADC.elf
Through physical debugging, it can be observed that the system is executing normally.
Interested friends can try to use MATLAB to generate a uint16_t type of FIR filter that can achieve input filtering for ADC.
Attachment: Main file source code
/********************************** (C) COPYRIGHT *********** *********************
* File Name : Main.c
* Author : WCH
* Version : V1.0
* Description: adc sampling filter example
*************************************************** *******************************/
#include "CH58x_common.h"
#include "CH58x_timer.h"
#include <discrete_control_system.h>
#include <z_transfer_function.h>
signed short RoughCalib_Value = 0; // ADC coarse adjustment deviation value
// Initialize the control system
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);
//Configure serial port debugging
DebugInit();
PRINT( "Start @ChipID=%02X\n", R8_CHIP_ID );
// Single channel sampling: select adc channel 0 for sampling, corresponding to PA4 pin, first perform data calibration function
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(); // Used to calculate the internal deviation of ADC and record it in the global variable RoughCalib_Value
// Initialize the control system
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);
// Start TIM0 as system clock
TMR0_TimerInit(6400);
TMR0_ITCfg( ENABLE, TMR0_3_IT_CYC_END );
while(1);
}
__INTERRUPT
__HIGH_CODE
void TMR0_IRQHandler( void ) //TMR0 timer interrupt
{
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); // Clear interrupt flag
// GPIOB_InverseBits( GPIO_Pin_15 );
}
}
|