In the world of robotics and automation movement is always been a component. In the past there are really two forms of motion control, open loop systems and closed loop systems. In an open loop system you would use some type of a stepper motor and digitally count the number of pulses and that number would be multiplied by some ratio that would give you some type of length. On the other side of the spectrum there are closed loop systems, which attach an encoder to the driving mechanism.
This encoder constantly provides feedback to the application on the distance traveled. Open loop systems had the advantage of expense, fewer components, mean fewer costs. The disadvantage in a open loop system is that you are always using a "best guess" scenario on how far the unit traveled. Closed loop systems are by far the best way of ensuring accuracy and repeat-ability, but has the disadvantage of cost and complexity.
Traditional encoders use various methods including light interrupters and hall effect sensors to describe positioning information. While these systems can be very accurate, particulate matter can interfere with a light interrupter and hall effect tended to not have as high resolution. What we have here today is something far more convenient, accurate, and economic. MikroElektronika has introduced the Magneto click as of this January and carried on the compact board is an Austria Micro Systems AS5048A rotary position sensor.
What sets the Magneto apart is the contact-less magnetic technology. Not only does this sensor work with no contact with the magnet, but it also can reside above or below the chip. What makes this even more incredible is the fact that it has 14bit resolution. That means that you have 16384 positions every 360 degrees of angular movement.
https://github.com/MikroElektronika/Click_Magneto_AS4058A
Setup
MikroE. has always gone the extra distance in bringing the barrier to entry into high tech a short journey. Our IDE makes added libraries just as easy as checking your email. With this in mind, we are going to be installing the Magneto click library and examine the code that makes it work.
- Get package or source code from GitHub.com Magneto Library Source Code or Libstock
- Unzip your platform ( ARM, PIC, FT90x, AVR, PIC32, DSPIC )
- Install libary using the Package Manager
- Open the IDE and start a new project
- Verify Click_Magneto is now visible in the Library manager, include it in your project by checking it
The first thing we want to do is include the magneto library into our search path. This can be easily done by selecting Project / Edit.
... add search path.
Include the magneto.h header file into our project by:
#include "magneto.h" void main() { while( 1 ) { //TODO: The real magic goes here } }
Our sensor works with the SPI bus, therefore we need to initialize the bus that our Magneto is attached to in order for communication to work. The SPI library should be selected in the Library manager when you selected the Magneto.
Initialization is best when moved away from the main function by placing all the initialization routines into a separate function.
#include "magneto.h" void system_init( void ); void system_init() { } void main() { system_init(); while( 1 ) { //TODO: The real magic goes here } }
You might be asking why there is a function prototype at the top. Every function in C needs to have a prototype. While it does work by leaving the main function at the bottom, if the prototype was missing and somehow the function was beneath the calling function, the compiler wouldn't be able to find it and errors would occur. Best practice is to always have a prototype for each function. The only exception to this rule is the main function and any ISR routines.
Let's initialize the bus. The AMS sensor is quite sensitive to speed. A maximum 5 Mhz is tolerated and what is a common mistake is that the sensor itself is slightly out of phase from the system clock. By this omission we need to take care that we sample the data coming from the slave on the falling edge. To do this, our initialization looks something like this:
void system_init() { /* SPI Init - NOTE* Magneto samples bits on the second clock transition */ SPI3_Init_Advanced( _SPI_FPCLK_DIV16, _SPI_MASTER | _SPI_8_BIT | _SPI_CLK_IDLE_LOW | _SPI_SECOND_CLK_EDGE_TRANSITION | _SPI_MSB_FIRST | _SPI_SS_DISABLE | _SPI_SSM_ENABLE | _SPI_SSI_1, &_GPIO_MODULE_SPI3_PC10_11_12 ); }
Once our bus is running, the only thing needed by the MikroE library is to initialize the Magneto click with some default settings. Since the library supports I2C and SPI modes, we need to specify the bus type, the address of the device if we were using I2C and how many devices are on the bus. As a side note, the AMS sensor allows for a unique form of chaining. We can chain a number of devices together without taking any additional pins.
The last argument is how many devices will be on the bus. For this demo we have only the 1 device on a SPI bus.
void system_init() { /* SPI Init - NOTE* Magneto samples bits on the second clock transition */ SPI3_Init_Advanced( _SPI_FPCLK_DIV16, _SPI_MASTER | _SPI_8_BIT | _SPI_CLK_IDLE_LOW | _SPI_SECOND_CLK_EDGE_TRANSITION | _SPI_MSB_FIRST | _SPI_SS_DISABLE | _SPI_SSM_ENABLE | _SPI_SSI_1, &_GPIO_MODULE_SPI3_PC10_11_12 ); magneto_init( MAGNETO_SPI, 0, 1 ); }
This is really all we need to get started except for one thing. When you press the build button you are going to be presented with an error.
Unresolved extern 'MAGNETO_CS'
The library requires that you declare what pin the chip select is on. I'm using the EasyMx PRO version 7 and placing the Magneto in the mikroBUS slot 1. The chip select for that port is PD13.
Add in your project the following:
#include "magneto.h" sbit MAGNETO_CS at GPIOD_ODR.B13; void system_init( void ); void system_init()
Build again add it should compile: Finished successfully: 19 Jan 2016, 09:01:15 Magneto_Demo.mcpar
Now you are ready to measure angular movement with 14bit precision.
Testing
According to the documentation, there are 3 ways to extract measurements from the sensor: Angles, Degrees, or Radians. Each is used is various purposes. Angles returns numbers between 0 - 16384, Degrees returns degrees in fractional increments from 0 - 359.9, and Radians are the converted degrees into radians. Each of the functions returns a pointer to either a int16_t or float. What you do with the information is up to you and your intended application.
What you may notice is that the functions that return measurements all look something like this:
Because the Magneto can be daisy chained together, you have multiple results when fetching a measurement. To make things easy you can create a pointer, and have the pointer be assigned to the return of a call to any of the measurement functions. For instance:
uint16_t *angles; angles = magneto_get_angles( 1 ); // Request measurement from device 1
That statement says get the angle from device 1. To use the angle you would dereference the pointer *angles.
In the case you have multiple sensors and you wanted to access all of them, then the following would retrieve all values:
uint16_t *angles; angles = magneto_get_angles( 0 ); // Request measurement from all devices if( angles[0] != angles[1] ) { move_arm( 1, angles[0] - angles[1] ); }
Now you can access all the values like you would an array.
Notes
Errors can happen depending on the setup and situation. Four errors are defined, framing errors, wrong command, wrong parity, and num of clocks. For the first 3 you should never see. These are errors related to the driver itself. The last error, however, will only happen if your clock speed is too fast for the sensor. If you think you are having errors, the library has a short function to check. After every operation you can call magneto_error() and it will return with a true or false if an error occurred. If an error does occur, you can retrieve the error by using the magneto_get_errors( device_num );
int16_t *angles; angles = magneto_get_angles( 1 ); if( magneto_error() ) { magneto_error_t *errors = magneto_get_errors( 1 ); if( *errors == MAGNETO_WRONG_NUM_OF_CLOCKS ) printf( "TOO FAST!" ); }
Example
/***************************************************************************** * Title : Magneto click Example * Filename : Magneto_clickARM.c * Author : RBL * Origin Date : 29/12/2015 * Notes : None *****************************************************************************/ /*************** MODULE REVISION LOG ***************************************** * * Date Software Version Initials Description * 29/12/15 .1 RBL Module Created. * *****************************************************************************/ /** * @file Magneto_clickARM.c * * @brief This module contains the example for the Magneto click and various * functions contained in the Magneto library. */ /***************************************************************************** * Includes *******************************************************************************/ #include "magneto.h" #include "built_in.h" #include "resources.h" #include/****************************************************************************** * Module Preprocessor Constants *******************************************************************************/ #define INNER_RADIUS 20.0f #define OUTER_RADIUS 60.0f #define X_CENTER 160.0f #define Y_CENTER 110.0f #define PI 3.14159265f /****************************************************************************** * Module Preprocessor Macros *******************************************************************************/ /****************************************************************************** * Module Typedefs *******************************************************************************/ /** * @struct Line beginning and end points in X and Y */ typedef struct { int x1; int y1; int x2; int y2; } line_t; /***************************************************************************** * Module Variable Definitions ****************************************************************************/ sbit MAGNETO_CS at GPIOD_ODR.B13; // TFT module connections unsigned int TFT_DataPort at GPIOE_ODR; sbit TFT_RST at GPIOE_ODR.B8; sbit TFT_RS at GPIOE_ODR.B12; sbit TFT_CS at GPIOE_ODR.B15; sbit TFT_RD at GPIOE_ODR.B10; sbit TFT_WR at GPIOE_ODR.B11; sbit TFT_BLED at GPIOE_ODR.B9; // End TFT module connections static bool volatile update_flag; /***************************************************************************** * Function Prototypes ****************************************************************************/ static void init_timer2( void ); static void tft_initialize( void ); static void tft_update( void ); static void system_init( void ); /***************************************************************************** * Function Implementations ****************************************************************************/ static void init_timer2() { RCC_APB1ENR.TIM2EN = 1; TIM2_CR1.CEN = 0; TIM2_PSC = 239; // 100ms TIM2_ARR = 62499; NVIC_IntEnable( IVT_INT_TIM2 ); TIM2_DIER.UIE = 1; TIM2_CR1.CEN = 1; } static void tft_initialize() { TFT_Init_ILI9341_8bit( 320, 280 ); TFT_BLED = 1; TFT_Set_Default_Mode(); TFT_Set_Pen( CL_WHITE, 1 ); TFT_Set_Brush( 1, CL_WHITE, 0, 0, 0, 0 ); TFT_Set_Font( TFT_defaultFont, CL_BLACK, FO_HORIZONTAL ); TFT_Fill_Screen( CL_WHITE ); TFT_Set_Pen( CL_BLACK, 1 ); TFT_Line( 20, 222, 300, 222 ); TFT_Set_Font( &Verdana12x13_Regular, CL_BLACK, FO_HORIZONTAL ); TFT_Write_Text( "EasyMx PRO v7 for STM32", 19, 223 ); TFT_Set_Font( &Verdana12x13_Regular, CL_RED, FO_HORIZONTAL ); TFT_Write_Text( "www.mikroe.com", 200, 223 ); TFT_Image( ( 320 - 220 ) / 2, 0, rudderangle_bmp, 1 ); TFT_Set_Font( TFT_defaultFont, CL_BLACK, FO_HORIZONTAL ); } static void tft_update() { static float last_angle; static line_t last_line; float *read_angle; line_t current_line; float total = 0; char tmp_txt[20]; int i; for( i = 0; i < 10; i++ ) { read_angle = magneto_get_degrees( 1 ); if( magneto_error() ) { magneto_error_t *error = magneto_get_errors( 1 ); TFT_Set_Font( TFT_defaultFont, CL_RED, FO_HORIZONTAL ); TFT_Write_Text( "ERROR", 138, 25 ); } total += *read_angle; } total /= 10.0f; TFT_Set_Font( TFT_defaultFont, CL_WHITE, FO_HORIZONTAL ); TFT_Write_Text( "ERROR", 138, 25 ); sprintf( tmp_txt, "%3.1f", last_angle ); TFT_Write_Text( tmp_txt, 10, 25 ); TFT_Set_Font( TFT_defaultFont, CL_BLACK, FO_HORIZONTAL ); sprintf( tmp_txt, "%3.1f", total ); TFT_Write_Text( tmp_txt, 10, 25 ); last_angle = total; TFT_Set_Pen( CL_WHITE, 4 ); TFT_Line( last_line.x1, last_line.y1, last_line.x2, last_line.y2 ); TFT_Set_Pen( CL_RED, 4 ); if( total <= 90.0f ) { current_line.x1 = ( int )( X_CENTER + ( INNER_RADIUS * sin( 90.0f * ( PI / 180.0f ) ) ) ); current_line.y1 = ( int )( Y_CENTER + ( INNER_RADIUS * -cos( 90.0f * ( PI / 180.0f ) ) ) ); current_line.x2 = ( int )( X_CENTER + ( OUTER_RADIUS * sin( 90.0f * ( PI / 180.0f ) ) ) ); current_line.y2 = ( int )( Y_CENTER + ( OUTER_RADIUS * -cos( 90.0f * ( PI / 180.0f ) ) ) ); } else if( total >= 270.0f ) { current_line.x1 = ( int )( X_CENTER + ( INNER_RADIUS * sin( 270.0f * ( PI / 180.0f ) ) ) ); current_line.y1 = ( int )( Y_CENTER + ( INNER_RADIUS * -cos( 270.0f * ( PI / 180.0f ) ) ) ); current_line.x2 = ( int )( X_CENTER + ( OUTER_RADIUS * sin( 270.0f * ( PI / 180.0f ) ) ) ); current_line.y2 = ( int )( Y_CENTER + ( OUTER_RADIUS * -cos( 270.0f * ( PI / 180.0f ) ) ) ); } else { current_line.x1 = ( int )( X_CENTER + ( INNER_RADIUS * sin( total * ( PI / 180.0f ) ) ) ); current_line.y1 = ( int )( Y_CENTER + ( INNER_RADIUS * -cos( total * ( PI / 180.0f ) ) ) ); current_line.x2 = ( int )( X_CENTER + ( OUTER_RADIUS * sin( total * ( PI / 180.0f ) ) ) ); current_line.y2 = ( int )( Y_CENTER + ( OUTER_RADIUS * -cos( total * ( PI / 180.0f ) ) ) ); } TFT_Line( current_line.x1, current_line.y1, current_line.x2, current_line.y2 ); memcpy( &last_line, ¤t_line, sizeof( line_t ) ); update_flag = false; } // Initialize all system peripherals static void system_init() { DisableInterrupts(); GPIO_Digital_Output( &GPIOD_BASE, _GPIO_PINMASK_13 ); /* SPI Init - NOTE* Magneto samples bits on the second clock transition */ SPI3_Init_Advanced( _SPI_FPCLK_DIV16, _SPI_MASTER | _SPI_8_BIT | _SPI_CLK_IDLE_LOW | _SPI_SECOND_CLK_EDGE_TRANSITION | _SPI_MSB_FIRST | _SPI_SS_DISABLE | _SPI_SSM_ENABLE | _SPI_SSI_1, &_GPIO_MODULE_SPI3_PC10_11_12 ); // Initialize display tft_initialize(); // Start timer init_timer2(); // Initialize magneto and check for success or failure if( magneto_init( MAGNETO_SPI, 0, 1 ) ) return; } void main() { system_init(); EnableInterrupts(); while( 1 ) { if( update_flag ) tft_update(); } } // ISR timer for signaling void timer2_interrupt() iv IVT_INT_TIM2 { TIM2_SR.UIF = 0; update_flag = true; } /*************** END OF FUNCTIONS ***************************************/
Summary
While the idea isn't new, the technology and method of angular measurement is revolutionary. Try submerging your optical encoder in a bath of toxic chemicals and then get back with me on how well that worked for you. On the other hand, magnetic flux doesn't care about what medium it lives in. In fact, magnetism works at any depth in the sea, how about a casing housing an optical encoder? The possibilities are tremendous and once the automation world grabs hold of this technology, I predict this will be the new standard in angular measurement.
References
wikipedia.org "Closed Loop Control" wikipedia 2015
wikipedia.org "Open Loop Control" wikipedia 2015