So far, I've only been in contact with python for two years, so I can't share these two languages. So far, I've only been in contact with python for two years. Of course, I still have to write this article with a long face. I'm not afraid of the ridicule of the big guys. I just make a record and give some guidance and Enlightenment to the dishes and chickens that are more delicious than me.
Key points of packaging
A good underlying package can make you feel more comfortable when calling at the top level, and it can also be easily transplanted to other projects
On the contrary, if the encapsulation is not appropriate, there will be great problems in both top-level invocation and portability.
Here are some key points I have summarized:
- Strong readability of function and type naming
- The interface should be simple and easy to use
- Reduce the coupling with other modules as much as possible
The essence is: multi-purpose structure, multi-purpose static
Design a package with practice
Take out a chestnut
Take the control code of a car as an example. From the perspective of car splitting, what are there on our car:
- electric machinery
- encoder
- motor drive
- Screen, buzzer and other devices that may interact with the outside world
- Attitude sensor, infrared sensor, etc
Packaging design
reel silk from cocoons -- make a painstaking investigation
I think it's best to write from top to bottom when encapsulating code, that is, write the top-level call first, write out the use mode of the function, consider the portability and call convenience, and then consider the bottom-level implementation. Of course, there will be multiple calls.
So, how should the code part be implemented to control the movement of a car?
Forward, backward, steering and the like are also necessary:
void CarMoveForward(void); void CarTurnLeft(void); void CarTurnRight(void);
That seems a little inappropriate? At what speed? How many degrees is the steering? Should I return something after the steering is completed?
Add the following:
void CarMoveForward(float speed); uint8_t CarTurnLeft(float angle); uint8_t CarTurnRight(float angle);
But how does this function control these two cars? How does the function know which control IO ports correspond to the left wheel of this car
Of course, some people will say that it is enough to put these things inside the function, but from the perspective of portability, the next ride may not be the pin, so you have to go to the package library to modify the contents of the library, which is very bad.
Therefore, here we need to introduce a structure to describe the attributes of the car.
struct CAR_s { struct Wheel_s leftwheel;//Structure corresponding to left wheel struct Wheel_s rightwheel; struct IMU_s imu;//Structure corresponding to attitude sensor struct PID_s anglepid;//Angle ring for steering //Of course, there's more than that }; void CarMoveForward(struct CAR_s *car,float speed); uint8_t CarTurn(struct CAR_s *car,float angle);//Note that angle can have positive and negative, so the two functions are combined
Through this improvement, we can pass in the structure parameters when calling, and access the structure parameters inside the function to control the car. If we need to modify the pins, we can modify the variable value in the structure.
Of course, a car is still too big, so I decompose it into several parts here: two driving wheels and a attitude sensor, which can be supplemented later.
The top-level function design has been completed, so we can start to enter the interior. Let's think about how to implement CarTurn function:
void CarTurn(struct CAR_s *car,float angle) { static uint8_t turnflag = 0; static float tarangle; float curangle = IMUgetyaw(car->imu); if(!turnflag) { tarangle = curangle + angle;//In fact, you can't add directly here, because the range of yaw is - 180 ~ 180. How to solve the problem turnflag=1; } float speedout = PIDCalc(&car->anglepid,tarangle,curangle); /*Turn in place by simply reversing two wheels*/ MotorCtrl(&car->leftwheel,speedout); MotorCtrl(&car->rightwheel,-speedout); if(tarangle-curangle<1&&tarangle-curangle>-1)//The simple addition and subtraction here can also be problematic { turnflag = 0; return 1;//When the condition is met, the steering is completed, and 1 is returned, so that the upper function does not continue to call the function } else return 0; }
It can be found that the structure will make the function more concise, which is also very good in terms of logic and readability. At the same time, there are many pits left in it: for example, the control motor and pid at the bottom, and the reading of imu
Let's continue. Take 6612 drive as an example
struct Wheel_s { /* Two control direction pins */ GPIO_TypeDef A0Port; uint16_t A0Pin; GPIO_TypeDef A1Port; uint16_t A1Pin; uint32_t *timccr; uint32_t *timarr; /*pid Speed loop*/ struct PID_s speedpid; /*Encoder part*/ int32_t speedraw;//Raw data of speed int32_t posraw;//Raw data of location uint32_t num;//Number of encoder lines struct Encoder_s encoder; }; /** * @brief Control motor speed * @param w Structure of wheel * @param s Target speed */ void MotorCtrl(Wheel_s *w,float s) { w->speedraw = encoderGet(w->encoder); float curspeed = (float)(w->speedraw/w->num); float pidout = PIDCalc(w->speedpid,s,curspeed); if(pidout>=0) { HAL_GPIO_WritePin(w->A0Port,w->A0Pin,1); HAL_GPIO_WritePin(w->A1Port,w->A1Pin,0); *w->timccr = *w->timarr*pidout/100; } else { HAL_GPIO_WritePin(w->A0Port,w->A0Pin,0); HAL_GPIO_WritePin(w->A1Port,w->A1Pin,1); *w->timccr = *w->timarr*(-pidout)/100; } }
Here we finally touch the bottom of the control, but the calculation of pid is still not shown. This article is just an idea, so the next code will not be released (purely I don't want to write it)