Tuesday, 25 October 2016

UP Board, Blinky in C#

                         (this is not my jewellery CNC blog, that's here : http://digital-jeweller.blogspot.com/)

Blinking an LED on the UP.. Sounds simple?

If you have done the below correctly you should have an outcome as this:
(or if you are lazy, download the project HERE)

UP LED FIRST....


The majority of "makers" being targeted by the myriad of new SBC's on the market are not professional coders, most are not even amateur coders.. for the large part they are people like me.. interested in learning a new technology, and willing to google for snippets and copy-paste a working program together. 

The success of boards like the Raspberry Pi and the Arduino series are largely due to their plain English, down to earth API's, aimed at "makers" with ample clear instructions and tutorials making the learning curve fast.  

SO: it was a little disappointing to have run into such a lot of headaches with my UP Board, and so little documentation to try figure it out.  Anyhow, getting back to Blink...

Lets examine this simplest of programs on a few other platforms.. lighting up an LED.


Arduino:
ledPin = 3; 
pinMode(ledPin, OUPUT);
digitalWrite(ledPin, HIGH)

RPi running  IoT: 
using Windows.Devices.Gpio; 
ledPin = gpio.OpenPin(3);
ledPin.SetDriveMode(GpioPinDriveMode.Output);
ledPin.Write(GpioPinValue.High);

RPi running  Rasbian: 
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.Board)
ledPin = 3
GPIO.setup(ledPin,GPIO.OUT)
GPIO.output(ledPin,GPIO.HIGH)

There is a commonality of implementation that makes it simple to understand the process.. 

include a library if required, 
set the pin direction, 
set the pin value. 


Now lets examine doing the the same on UP Board, using C# in windows 10.


Firstly the SETUP Steps:

You will need to download the SDK and Framework, below are the ones used in this example. I don't maintain these files so they are subject to change. 

SDK: http://www.dropbox.com/s/uamv9p4tt5881jm/Dio.rar?dl=0
FrameWork : http://www.dropbox.com/s/ha0nxu0kmdnfta9/Hi-Safe_v2_20161017.rar?dl=0

The framework contains some drivers. Install the drivers using the adequate advice in the PDF in the folder. 

In the SDK is an example project called DIO, (written in CS).

Copy the DIO project to a handy location and copy out the file aaeonEAPI.dll. Now open the DIO project in VS and leave it open.. we will need to copy some stuff over later.

(if you attempt to compile the project as is in VS15 you will hit a wall, search my name CeeBee on the UP forum for instructions on how to bypass this issue, but for now we only want to copy files from this project, not run it, so onwards...)

Create a new Project:

In your new project right click the project name in solution explorer, and then  [ADD EXISTING]. Add the aaeonEAPI.dll file we copied out earlier, then set the aaeonEAPI.dll to "copy always" in properties.  

Now lets go back to the DIO projects main application code and copy and paste the lines near the top of it which import the DLL calls from the aaeonEAPI.dll, into your program. 

Open and copy all the content EAPI.CS file main body.

(its been done in the region wrappers in the code below)

Now finally you are ready to start actually coding.. you can close the DIO example.

Returning to our examples above we can immediately write the following code and expect a result.

EApiLibInitialize();
LedPin =3;
EApiGPIOSetDirection(EAPI.EAPI_GPIO_GPIO_ID((UInt32)(ledPin)), 0xFFFFFFFF, 0)
EApiGPIOSetLevel(EAPI.EAPI_GPIO_GPIO_ID((UInt32)(ledPin)), 0xFFFFFFFF, 1)

BUT... It didn't work, did it? The current API has a glitch, either by mistake or by design the pin numbering is out by -1... changing 3 to 2 in example above woudl make pin 3 light up... 

BUT,  what is input, what is output, what is high, what is low, and boy is that code ugly, all those uint's and 0xFFFFFFFF's which we have no idea about, why are we looking at them?

SO, lets fix that.. and make it look prettier.. 
(and fix that pesky bug)

Add these variables to the top of your code,and then add these two voids into your code 
(these are the most basic examples to get it working without any error checking code!!)

private uint HIGH = 1, LOW = 0, INPUT = 1, OUTPUT = 0;

public void PinMode(int pin, uint dir)
pin = pin - 1;
EApiGPIOSetDirection(EAPI.EAPI_GPIO_GPIO_ID((UInt32)(pin)), 0xFFFFFFFF, dir);}

public void DigitalWrite(int pin, uint state)
pin = pin - 1;
EApiGPIOSetLevel(EAPI.EAPI_GPIO_GPIO_ID((UInt32)(pin)), 0xFFFFFFFF, state);}

Which will now allow you to write code using a more common kind of expression like


EApiLibInitialize();
ledPin= 3;
PinMode(ledPin,OUTPUT);
DigitalWrite(ledPin,HIGH);

and PIN3 will now fire up happily... 

This is also now far more legible, noting you can call these voids anything you like, or return an error etc.

..its my void, and I'll can int if I want to... <sung to "It's my Party" - Leslie Gore>

If you own your board, and the code is for you, to be added to existing projects, you don't need to go much past this, 

For the rest, lets move onto SuperBlinky... 


BLINKY

So the first actual "Blinky" program for the UP board... .. based on the above cleanup and fixes, its a little beefier, with selectable pin, selectable millisecond delay and the number of repeats.. and some basic error catch.

(you can delete the form, BlinkPin and Start_Click and basically have a template ready to start working in.)

Methods created are :

EnableSDIO() 
which with return with an int ERR if there is one.

SetPinDirection(int pin, uint direction)
which will accept the pin number and either INPUT or OUTPUT as a uint variable, it will return an int err if it went kooky on you.(status.text is updated)

GetPinDirection(int pin, uint direction)
which will accept the pin number and a variable to pass to the API for the pin direction. it will return and int of either ERR (status.text is updated) or 1(input) or 0(output).

SetPinState(int pin, uint state)
which will accept the pin number and either HIGH or LOW as a uint variable, it will return an int ERR if it went kooky on you. (status.text is updated)

GetPinState(int pin, uint state)
which will accept the pin number and a variable to pass to the API for the pin state. it will return and int of either ERR (status.text is updated) or 1(high) or 0(low).

(errors and values as int as its a PITA to keep having to convert between uint's to do if/then compares to other ints, change as required)

You will need to create a form with these components



And this code. 


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UP_Blinky
{
    public partial class Blinky : Form
    {
        #region Import DLL Calls
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiGPIOGetDirectionCaps")]
        public static extern UInt32 EApiGPIOGetDirectionCaps(uint Id, ref UInt32 pInputs, ref UInt32 pOutputs);
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiGPIOGetDirection")]
        public static extern UInt32 EApiGPIOGetDirection(UInt32 Id, UInt32 Bitmask, ref UInt32 pDirection);
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiGPIOSetDirection")]
        public static extern UInt32 EApiGPIOSetDirection(UInt32 Id, UInt32 Bitmask, UInt32 Direction);
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiGPIOGetLevel")]
        public static extern UInt32 EApiGPIOGetLevel(UInt32 Id, UInt32 Bitmask, ref UInt32 pLevel);
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiGPIOSetLevel")]
        public static extern UInt32 EApiGPIOSetLevel(UInt32 Id, UInt32 Bitmask, UInt32 Level);
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiGPIOGetCaps")]
        public static extern UInt32 EApiGPIOGetCaps(UInt32 Id, ref UInt32 PinCount, ref UInt32 pDioDisable);
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiLibInitialize")]
        public static extern UInt32 EApiLibInitialize();
        [DllImport("aaeonEAPI.dll", EntryPoint = "EApiLibUnInitialize")]
        public static extern UInt32 EApiLibUnInitialize();
        #endregion
        #region My Variables
        private uint HIGH = 1;
        private uint LOW = 0;
        private uint INPUT = 1;
        private uint OUTPUT = 0;
        #endregion
        #region Status Codes
        public const UInt32 EAPI_STATUS_NOT_INITIALIZED = 0xFFFFFFFF;
        public const UInt32 EAPI_STATUS_INITIALIZED = 0xFFFFFFFE;
        public const UInt32 EAPI_STATUS_ALLOC_ERROR = 0xFFFFFFFD;
        public const UInt32 EAPI_STATUS_DRIVER_TIMEOUT = 0xFFFFFFFC;
        public const UInt32 EAPI_STATUS_INVALID_PARAMETER = 0xFFFFFEFF;
        public const UInt32 EAPI_STATUS_INVALID_BLOCK_ALIGNMENT = 0xFFFFFEFE;
        public const UInt32 EAPI_STATUS_INVALID_BLOCK_LENGTH = 0xFFFFFEFD;
        public const UInt32 EAPI_STATUS_INVALID_DIRECTION = 0xFFFFFEFC;
        public const UInt32 EAPI_STATUS_INVALID_BITMASK = 0xFFFFFEFB;
        public const UInt32 EAPI_STATUS_RUNNING = 0xFFFFFEFA;
        public const UInt32 EAPI_STATUS_UNSUPPORTED = 0xFFFFFCFF;
        public const UInt32 EAPI_STATUS_NOT_FOUND = 0xFFFFFBFF;
        public const UInt32 EAPI_STATUS_TIMEOUT = 0xFFFFFBFE;
        public const UInt32 EAPI_STATUS_BUSY_COLLISION = 0xFFFFFBFD;
        public const UInt32 EAPI_STATUS_READ_ERROR = 0xFFFFFAFF;
        public const UInt32 EAPI_STATUS_WRITE_ERROR = 0xFFFFFAFE;
        public const UInt32 EAPI_STATUS_MORE_DATA = 0xFFFFF9FF;
        public const UInt32 EAPI_STATUS_ERROR = 0xFFFFF0FF;
        public const UInt32 EAPI_STATUS_SUCCESS = 0x00000000;
        #endregion
        #region Board Hardware Info Strings
        public const UInt32 EAPI_ID_BOARD_MANUFACTURER_STR = 0;
        public const UInt32 EAPI_ID_BOARD_NAME_STR = 1;
        public const UInt32 EAPI_ID_BOARD_REVISION_STR = 2;
        public const UInt32 EAPI_ID_BOARD_SERIAL_STR = 3;
        public const UInt32 EAPI_ID_BOARD_BIOS_REVISION_STR = 4;
        public const UInt32 EAPI_ID_BOARD_HW_REVISION_STR = 5;
        public const UInt32 EAPI_ID_BOARD_PLATFORM_TYPE_STR = 6;
        public const UInt32 EAPI_ID_GET_EAPI_SPEC_VERSION = 0;
        public const UInt32 EAPI_ID_BOARD_BOOT_COUNTER_VAL = 1;
        public const UInt32 EAPI_ID_BOARD_RUNNING_TIME_METER_VAL = 2;
        public const UInt32 EAPI_ID_BOARD_PNPID_VAL = 3;
        public const UInt32 EAPI_ID_BOARD_PLATFORM_REV_VAL = 4;
        public const UInt32 EAPI_ID_AONCUS_HISAFE_FUCTION = 5;
        public const UInt32 EAPI_ID_BOARD_DRIVER_VERSION_VAL = 0x10000; 
        public const UInt32 EAPI_ID_BOARD_LIB_VERSION_VAL = 0x10001; 
        public const UInt32 EAPI_ID_HWMON_CPU_TEMP = 0x20000;
        public const UInt32 EAPI_ID_HWMON_CHIPSET_TEMP = 0x20001;
        public const UInt32 EAPI_ID_HWMON_SYSTEM_TEMP = 0x20002;  
        public const UInt32 EAPI_KELVINS_OFFSET = 2731;
        public static UInt32 EAPI_ENCODE_CELCIUS(UInt32 Celsius)
        {
            return (Celsius * 10) + EAPI_KELVINS_OFFSET;
        }
        public static UInt32 EAPI_DECODE_CELCIUS(UInt32 Celsius)
        {
            return ((Celsius) - EAPI_KELVINS_OFFSET) / 10;
        }
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_VCORE = 0x21004;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_2V5 = 0x21008;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_3V3 = 0x2100C;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_VBAT = 0x21010;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_5V = 0x21014;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_5VSB = 0x21018;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_12V = 0x2101C;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_DIMM = 0x21020;
        public const UInt32 EAPI_ID_HWMON_VOLTAGE_3VSB = 0x21024;
        public const UInt32 EAPI_ID_HWMON_FAN_CPU = 0x22000;
        public const UInt32 EAPI_ID_HWMON_FAN_CHIPSET = 0x22001;
        public const UInt32 EAPI_ID_HWMON_FAN_SYSTEM = 0x22002;
        public const UInt32 EAPI_ID_BACKLIGHT_1 = 0;
        public const UInt32 EAPI_ID_BACKLIGHT_2 = 1;
        public const UInt32 EAPI_ID_BACKLIGHT_3 = 2;
        public const UInt32 EAPI_BACKLIGHT_SET_ON = 0;
        public const UInt32 EAPI_BACKLIGHT_SET_OFF = 0xFFFFFFFF;
        public const UInt32 EAPI_BACKLIGHT_SET_DIMEST = 0;
        public const UInt32 EAPI_BACKLIGHT_SET_BRIGHTEST = 255;
        public const UInt32 EAPI_ID_STORAGE_STD = 0;
        public const UInt32 EAPI_ID_I2C_EXTERNAL = 0;
        public const UInt32 EAPI_ID_I2C_LVDS_1 = 1;
        public const UInt32 EAPI_ID_I2C_LVDS_2 = 2;
        public UInt32 EAPI_I2C_ENC_7BIT_ADDR(UInt32 x)
        {
            return ((x) & 0x07F) << 1;
        }
        public UInt32 EAPI_I2C_DEC_7BIT_ADDR(UInt32 x)
        {
            return ((x) >> 1) & 0x07F;
        }
        #endregion
        #region GPIO
        public static UInt32 EAPI_GPIO_GPIO_ID(UInt32 GPIO_NUM)
        {
            return GPIO_NUM;
        }
        public const UInt32 EAPI_GPIO_GPIO_BITMASK = 1;
        public UInt32 EAPI_ID_GPIO_GPIO00 = EAPI_GPIO_GPIO_ID(0);
        public UInt32 EAPI_ID_GPIO_GPIO01 = EAPI_GPIO_GPIO_ID(1);
        public UInt32 EAPI_ID_GPIO_GPIO02 = EAPI_GPIO_GPIO_ID(2);
        public UInt32 EAPI_ID_GPIO_GPIO03 = EAPI_GPIO_GPIO_ID(3);
        public UInt32 EAPI_ID_GPIO_GPIO04 = EAPI_GPIO_GPIO_ID(4);
        public UInt32 EAPI_ID_GPIO_GPIO05 = EAPI_GPIO_GPIO_ID(5);
        public UInt32 EAPI_ID_GPIO_GPIO06 = EAPI_GPIO_GPIO_ID(6);
        public UInt32 EAPI_ID_GPIO_GPIO07 = EAPI_GPIO_GPIO_ID(7);
        public static UInt32 EAPI_GPIO_BANK_ID(UInt32 GPIO_NUM)
        {
            return (0x10000 | ((GPIO_NUM) >> 5));
        }
        public static int EAPI_GPIO_BANK_MASK(int GPIO_NUM)
        {
            return (0x01 << (GPIO_NUM & 0x1F));
        }
        public UInt32 EAPI_ID_GPIO_BANK00 = EAPI_GPIO_BANK_ID(0);
        public UInt32 EAPI_ID_GPIO_BANK01 = EAPI_GPIO_BANK_ID(32);
        public UInt32 EAPI_ID_GPIO_BANK02 = EAPI_GPIO_BANK_ID(64);
        public const UInt32 EAPI_GPIO_BITMASK_SELECT = 1;
        public const UInt32 EAPI_GPIO_BITMASK_NOSELECT = 0;
        public const UInt32 EAPI_GPIO_LOW = 0;
        public const UInt32 EAPI_GPIO_HIGH = 1;
        public const UInt32 EAPI_GPIO_INPUT = 1;
        public const UInt32 EAPI_GPIO_OUTPUT = 0;
        #endregion   
        public Blinky()
        {
            InitializeComponent();
            EnableSDIO();
        }
        #region Helpers
        private int EnableSDIO()
        {
            uint err = EApiLibInitialize();
            if (err != EAPI_STATUS_INITIALIZED)
            {
                Status.Text = "Library did not Load";
                Start.Enabled = false;
                return(int)err;
            }
            Status.Text = "Library good";
            return (int)err;
        }
        public int SetPinDirection(int pin, uint direction)
        {
            uint err;
            pin = pin - 1;
            err = EApiGPIOSetDirection(EAPI_GPIO_GPIO_ID((UInt32)(pin)), 0xFFFFFFFF, direction);
            if (err > 0)
            {
                Status.Text = "Error #" + err;
            }
            return(int)err;
        }
        public int GetPinDirection(int pin, uint direction)
        {
            uint err;
            pin = pin - 1;
            err = EApiGPIOGetDirection(EAPI_GPIO_GPIO_ID((UInt32)(pin)), 0xFFFFFFFF, ref direction);
            if (err > 0)
            {
                Status.Text = "Error #" + err;
                return (int)err;
            }
            return (int)direction;
        }
        public int SetPinState(int pin, uint state)
        {
            uint err;
            pin = pin - 1;
            err = EApiGPIOSetLevel(EAPI_GPIO_GPIO_ID((UInt32)(pin)), 0xFFFFFFFF, state);
            if (err > 0)
            {
                Status.Text = "Error #" + err;
            }
            return (int)err;
        }
        public int GetPinState(int pin, uint state)
        {
            uint err;
            pin = pin - 1;
            err = EApiGPIOGetLevel(EAPI_GPIO_GPIO_ID((UInt32)(pin)), 0xFFFFFFFF, ref state);
            if (err > 0)
            {
                Status.Text = "Error #" + err;
                return(int)err;
            }
            return (int)state;
        }
        private void UP_Blinky_Closing(object sender, FormClosingEventArgs e)
        {
            //when we close the form, close the library
            EApiLibUnInitialize();
        }
        #endregion

        private void Start_Click(object sender, EventArgs e)
        {
            int Pin = Decimal.ToInt32(PinNumberSelect.Value);
            int speed = Decimal.ToInt32(Speed.Value);
            int repeat = Decimal.ToInt32(repeatvalue.Value);
            BlinkPin(Pin,speed,repeat);
        }
        private async void BlinkPin(int Pin,int speed,int repeat)
        {
            SetPinDirection(Pin, OUTPUT);
            Status.Text = "Pin #" + Pin + " Set as OUTPUT";
            await Task.Delay(TimeSpan.FromMilliseconds(speed));

            for (int i = 0; i < repeat; i++)
            {           
                Status.Text = "Pin #" + Pin + " Set HIGH";
                LedBlinker.BackColor = Color.Red;
                SetPinState(Pin, HIGH);
                await Task.Delay(TimeSpan.FromMilliseconds(speed));

                SetPinState(Pin, LOW);
                Status.Text = "Pin #" + Pin + " Set LOW";
                LedBlinker.BackColor = Color.Black;
                await Task.Delay(TimeSpan.FromMilliseconds(speed));
            }
        }
    }
}

and hopefully you get the same result as me, a nice easy to use blinker app! 

(To keep yourself sane, use #region ~ #endregion wrappers to hide the stuff you don't want to see while you are working so your actual code ends up looking like bleow)


In Conclusion:

I still don't know what a bitmask is, or how and why and when it would be used, 
I still don't know how to write a PWM value, or read an analog pin... 

Much work to be done deciphering the API documentation... 

4 comments:

  1. Thanks chris , i have some doubt , whenever i run the app , i am getting like lib not loaded. can help me out here please

    ReplyDelete
  2. It means you have not installed the drivers and or copied the dlls over. Read the post carefully.

    ReplyDelete
  3. Chris-

    Is there any way you can share your code? None of the source links on this blog work. It seems that your google drive link is also down.
    https://drive.google.com/open?id=0B5yCbfbC75zDbUUwZ0lidUtmYlU

    ReplyDelete
  4. Awesome starter. Thank you.

    ReplyDelete