Semana 2¶
Propósito de aprendizaje¶
Analizar la plataforma de hardware y software del controlador que se empleará como interfaz entre los sensores-actuadores y las plataformas de software interactivas a utilizar en el curso.
Construir aplicaciones simples para el controlador con el fin de explorar algunas posibilidades y características de su plataforma de software.
Actividad de aprendizaje¶
Se realizará las SEMANA 2 y 3 (julio 13/julio 20).
Lee con detenimiento el código de honor y luego los pasos que debes seguir para evidenciar esta actividad.
Código de honor¶
Para realizar este reto se espera que hagas lo siguiente:
- Colabora con tus compañeros cuando así se indique.
- Trabaja de manera individual cuando la actividad así te lo proponga.
- Usa solo la documentación oficial del framework del controlador y .NET de Microsoft.
- NO DEBES utilizar sitios en Internet con soluciones o ideas para abordar el problema.
- NO DEBES hacer uso de foros.
- ¿Entonces qué hacer si no me funciona algo? Te propongo que experimentes, crea hipótesis, experimenta de nuevo, observa y concluye.
- NO OLVIDES, este curso se trata de pensar y experimentar NO de BUSCAR soluciones en Internet.
Enunciado:¶
Debes controlar el funcionamiento algunos sensores, actuadores y tareas desde el computador.
- Debes crear dos aplicaciones: una para el PC y otra para tu controlador.
- La aplicación del PC la debes realizar usando Visual Studio y será del tipo Consola con .NET framework.
- La aplicación del PC y del controlador interactuarán por medio de un modelo cliente servidor. La aplicación del PC será el cliente y la del controlador el servidor.
- Para la aplicación del controlador:
- Crea 4 tareas concurrentes.
- La tarea uno encenderá y apagará continuamente un LED a 1 Hz; la tarea 2 otro LED a 5 Hz; la tarea 3 otro LED a 7Hz; la tarea 4 recibirá comandos para leer un sensor digital, leer un sensor analógico, modificar un actuador digital, modificar un actuador analógico por PWM.
- En la aplicación del PC debes solicitarle al usuario comandos para interactuar con la
aplicación del controlador:
- Un comando para modificar la frecuencia de cada una de las tareas 1, 2 y 3. Debes especificar la tarea y la frecuencia.
- Para la tarea 4 define comandos que te permitan seleccionar el sensor/actuador y los valores respectivos.
- Ten presente que solo podrás comunicarte con el controlador una vez tengas toda la información, es decir, no debes hacer envíos parciales.
- El PC preguntará si se deseas continuar con la aplicación o terminar.
¿Qué debes entregar?¶
- Crea una carpeta principal. Guarda allí dos carpetas más, cada uno con el proyecto para el PC y para el controlador. Guarda los proyectos completos.
- En la carpeta principal guarda una copia de la rúbrica con tu autoevaluación.
- En la carpeta principal guarda un archivo .pdf donde colocarás cuatro cosas:
- La versión de Visual Studio utilizada.
- La versión del software para programar el controlador.
- UN ENLACE a tu ONE DRIVE donde estará alojado el video de sustentación.
- Una tabla de contenidos que indique el instante de tiempo en el cual se pueden encontrar cada una de las secciones solicitas para el video.
- Comprime la carpeta principal en formato .ZIP
- Entrega el archivo .ZIP aquí.
¿Qué deberá tener el video de sustentación?¶
- Máximo 20 minutos: debes planear el video tal como aprendiste en segundo semestre en tu curso de narrativa audiovisual.
- Cuida la calidad del audio y del video.
- Sección 1: introducción, donde dirás tu nombre y si realizaste el RETO completo. Si no terminaste indica claramente qué te faltó y por qué.
- Sección 2: muestra que tus dos programas compilan correctamente y sin errores o advertencias problemáticas.
- Sección 3: Realiza una demostración del funcionamiento donde ilustres todos los aspectos solicitados.
- Define un conjuntos de vectores de prueba donde indiques los datos de entrada y el resultado esperado.
- Aplica los vectores de prueba y muestra que si producen los valores esperados.
- Sección 4: explica la arquitectura de las aplicaciones. Utiliza una aplicación de WhiteBoard para esto.
- Tus explicaciones deben ser claras, precisas y completas. No olvides planear bien tu video de sustentación.
- Debes explicar las partes de la aplicación, la función que realiza cada parte y sus propiedades.
- Debes explicar las relaciones entre las partes, cómo funcionan esas relaciones y sus propiedades
- Sección 5: protocolo de integración entre las aplicaciones.
- Debes explicar claramente cómo se comunicarán tus aplicaciones.
- Muestra de manera detallada los pasos que deben realizar cada una de las aplicaciones. Te recomiendo utilizar un diagrama de secuencias.
Trayecto de acciones, tiempos y formas de trabajo¶
Fase 1 (motivación)¶
- Fecha: julio 13 de 2020 - 2 p.m.
- Descripción: asiste al encuentro sincrónico donde se introducirá la actividad de aprendizaje de la unidad 2 correspondiente a las semanas 2 y 3.
- Recursos: ingresa al grupo de Teams
- Duración de la actividad: 20 minutos sincrónicos.
- Forma de trabajo: grupal
Fase 2 (diagnóstico-repaso)¶
- Fecha: julio 13 de 2020
- Descripción: lee las preguntas y ejercicios orientadores para autoevaluar si tienes los conocimientos necesarios para abordar el RETO.
- Recursos: realiza esta guía.
- Duración de la actividad: 1 hora 10 minutos
- Forma de trabajo: individual con solución de dudas en tiempo real
Fase 3 (fundamentación)¶
- Fecha: julio 13-14 de 2020
- Descripción: realiza las lecturas donde se explican los fundamentos conceptuales de la plataforma de software utilizada para la construcción de los programas del controlador.
- Recursos: observa este material.
- Duración de la actividad: 1 hora de trabajo autónomo
- Forma de trabajo: individual
Fase 4 (ejercicios y discusión)¶
- Fecha: julio 13-14-15 de 2020
- Descripción: realiza los ejercicios propuestos. Acuerda reuniones con tus compañeros para trabajar de manera colaborativa
- Recursos:
- realiza estos Ejercicios.
- Duración de la actividad: 4 horas de trabajo autónomo y colaborativo. Acuerda reuniones con tus compañeros.
- Forma de trabajo: individual y colaborativa.
Fase 5 (retroalimentación):¶
- Fecha: julio 15 de 2020 - 2 p.m.
- Descripción: encuentro sincrónico para compartir y discutir los ejercicios.
- Recursos:
- Ingresar al grupo de Teams
- Corrige tus ejercicios (acciones de mejora)
- Duración de la actividad: 50 minutos de discusión y 50 minutos para que hagas las acciones de mejora sobre tu trabajo.
- Forma de trabajo: colaborativo con solución de dudas en tiempo real y trabajo individual en la acción de mejora.
Ejercicios¶
Ejercicio 1: explorando la carpeta de arduino¶
- Descarga una versión .zip del IDE de Arduino.
- Descomprime el archivo.
- Abre la carpeta y explore la estructura de directorios
- ¿Qué contiene la carpeta drivers? ¿Para qué sirve?
- ¿Qué contiene la carpeta examples y cuál es la relación con los ejemplos del IDE de arduino.
- ¿Qué contiene la carpeta libraries?
- Abre la carpeta hardware/arduino/avr
- ¿Qué contiene esta carpeta?
- Abre la carpeta hardware/arduino/avr/cores/arduino
- ¿Qué contiene esta carpeta?
Ejercicio 2: modificar el código fuente de arduino¶
Tomando con referencia el ejercicio anterior:
- Busca el archivo main.cpp.
- Modifica este archivo de tal manera que antes y después de llamar la función loop se envíe por el puerto serial el valor que devuelve la función millis().
- Salva el archivo main.cpp con los cambios.
- Abre el IDE de arduino y carga el ejemplo Blink.
- Abre la consola.
- ¿Qué puedes concluir?
Ejercicio 3¶
- ¿Recuerdas el ejemplo BlinkWithoutDelay?
- Busca de nuevo el ejemplo.
- Programa el arduino.
- Abre la consola.
- Compara con el ejercicio anterior.
- ¿Qué puedes concluir con respecto a la función delay?
Ejercicio 4¶
- ¿Recuerdas el ejemplo BlinkWithoutDelay?
- Busca de nuevo el ejemplo.
- Programa el arduino.
- Abre la consola.
- Compara con el ejercicio anterior.
- ¿Qué puedes concluir con respecto a la función delay?
- Una vez termines, no olvides dejar el archivo main.cpp como estaba originalmente.
Ejercicio 5¶
Analiza el siguiente código:
1 2 3 4 5 6 7 8 9 10 | void setup() {
Serial.begin(115200);
}
void loop() {
uint8_t counter = 20;
counter++;
Serial.println(counter);
delay(100);
}
|
Compara el código anterior con este:
1 2 3 4 5 6 7 8 9 10 | void setup() {
Serial.begin(115200);
}
void loop() {
static uint8_t counter = 20;
counter++;
Serial.println(counter);
delay(100);
}
|
Ahora compara con este otro código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | uint8_t counter = 5;
void setup() {
Serial.begin(115200);
}
void incCounter() {
static uint8_t counter = 10;
counter++;
Serial.print("Counter in incCounter: ");
Serial.println(counter);
}
void loop() {
static uint8_t counter = 20;
counter++;
Serial.print("Counter in loop: ");
Serial.println(counter);
incCounter();
Serial.print("Counter outside loop: ");
Serial.println(::counter);
::counter++;
delay(500);
}
|
¿Qué puedes concluir?
Ejercicio 6¶
Analiza el siguiente ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const uint8_t ledPin = 3;
uint8_t ledState = LOW;
uint32_t previousMillis = 0;
const uint32_t interval = 1000;
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop() {
uint32_t currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
}
|
Utilizando como referencia el código anterior crea un programa que encienda y apague tres LEDs a 1 Hz, 5 Hz y 7 Hz respectivamente.
Ejercicio 7¶
Vamos a analizar uno de los ejemplos que vienen con el SDK de arduino. Este ejemplo nos permite ver cómo podemos hacer uso de los arreglos para manipular varios LEDs:
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 | int timer = 100; // The higher the number, the slower the timing.
int ledPins[] = {
2, 7, 4, 6, 5, 3
}; // an array of pin numbers to which LEDs are attached
int pinCount = 6; // the number of pins (i.e. the length of the array)
void setup() {
// the array elements are numbered from 0 to (pinCount - 1).
// use a for loop to initialize each pin as an output:
for (int thisPin = 0; thisPin < pinCount; thisPin++) {
pinMode(ledPins[thisPin], OUTPUT);
}
}
void loop() {
// loop from the lowest pin to the highest:
for (int thisPin = 0; thisPin < pinCount; thisPin++) {
// turn the pin on:
digitalWrite(ledPins[thisPin], HIGH);
delay(timer);
// turn the pin off:
digitalWrite(ledPins[thisPin], LOW);
}
// loop from the highest pin to the lowest:
for (int thisPin = pinCount - 1; thisPin >= 0; thisPin--) {
// turn the pin on:
digitalWrite(ledPins[thisPin], HIGH);
delay(timer);
// turn the pin off:
digitalWrite(ledPins[thisPin], LOW);
}
}
|
Ejercicio 8¶
El siguiente código muestra cómo puedes encapsular completamente el código del ejercicio 6 en tareas.
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 | void setup() {
task1();
task2();
}
void task1(){
static uint32_t previousMillis = 0;
static const uint32_t interval = 1250;
static bool taskInit = false;
static const uint8_t ledPin = 3;
static uint8_t ledState = LOW;
if(taskInit == false){
pinMode(ledPin, OUTPUT);
taskInit = true;
}
uint32_t currentMillis = millis();
if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
void task2(){
static uint32_t previousMillis = 0;
static const uint32_t interval = 370;
static bool taskInit = false;
static const uint8_t ledPin = 5;
static uint8_t ledState = LOW;
if(taskInit == false){
pinMode(ledPin, OUTPUT);
taskInit = true;
}
uint32_t currentMillis = millis();
if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
void loop() {
task1();
task2();
}
|
Una de las ventajas del código anterior es que favorece el trabajo en equipo. Nota que se puede entregar a cada persona del equipo una tarea. Finalmente, uno de los miembros del equipo podrá integrar todas las tareas así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | void task1(){
// CODE
}
void task2(){
// CODE
}
void task3(){
// CODE
}
void setup() {
task1();
task2();
task3();
}
void loop() {
task1();
task2();
task3();
}
|
Analiza detenidamente el código anterior. Experimenta y asegurate de entenderlo perfectamente antes de continuar.
Ejercicio 9¶
Observa detenidamente el código de las siguientes tareas. ¿Es muy similar, verdad? En este ejercicio veremos una construcción interesante de C++ que permite reutilizar código. Nota que el código de las tareas 1 y 2 es prácticamente el mismo, solo que está actuando sobre diferentes datos.
¿Cómo así? ¿Recuerdas tu curso de programación orientado a objetos?
Analiza por partes. Primero, la inicialización de la tarea:
Para la tarea 1 (task1):
1 2 3 4 | if(taskInit == false){
pinMode(ledPin, OUTPUT);
taskInit = true;
}
|
Para la tarea 2 (task2):
1 2 3 4 | if(taskInit == false){
pinMode(ledPin, OUTPUT);
taskInit = true;
}
|
En el código anterior cada tarea tiene una variable que permite activar el código solo un vez, es decir, cuando taskInit es false. Esto se hace así para poder inicializar el puerto de salida donde estará el LED conectado. Recuerde que esto se hace solo una vez cuando llamemos taskX() (X es 1 o 2) en la función setup().
El código que se llamará repetidamente en la función loop:
Para la tarea 1:
1 2 3 4 5 6 7 8 9 | if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
|
Para la tarea 2:
1 2 3 4 5 6 7 8 9 10 | uint32_t currentMillis = millis();
if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
|
Nota que los datos sobre los que actúa cada código, aunque tienen el mismo nombre son datos distintos:
Para la tarea 1:
1 2 3 4 5 | static uint32_t previousMillis = 0;
static const uint32_t interval = 1250;
static bool taskInit = false;
static const uint8_t ledPin = 3;
static uint8_t ledState = LOW;
|
Para la tarea 2:
1 2 3 4 5 | static uint32_t previousMillis = 0;
static const uint32_t interval = 370;
static bool taskInit = false;
static const uint8_t ledPin = 5;
static uint8_t ledState = LOW;
|
Pero ¿Por qué son distintos? porque estamos declarando las variables como estáticas dentro de cada tarea. Esto implica que las variables son privadas a cada función pero se almacenan en memoria como si fueran variables globales.
¿Entendiste? No avances si esto no está claro.
Esto introduce la siguiente pregunta: ¿Qué tal si pudiéramos tener el mismo código, pero cada vez que lo llamemos indicarle sobre que datos debe actuar? Pues lo anterior es posible en C++ usando una construcción conocida como clase.
La clase nos permite definir un nuevo tipo dato y los algoritmos que se pueden aplicar a ese nuevo tipo de dato. En este caso, necesitamos que cada tarea pueda tener sus propias variables para previousMillis, interval, ledPin, ledState.
1 2 3 4 5 6 7 | class LED{
private:
uint32_t previousMillis;
const uint32_t interval;
const uint8_t ledPin;
uint8_t ledState = LOW;
};
|
De esta manera en cada tarea podremos crear un nuevo LED así:
1 2 3 | void task1(){
static LED led;
}
|
1 2 3 | void task2(){
static LED led;
}
|
A cada nuevo LED se le conoce como un objeto. led es la variable por medio de las cuales podremos acceder a cada uno de los objetos creados en task1 y task2.
Notas:
- Cada objeto es independiente, es decir, cada objeto tiene su propia copia de cada variable definida en la clase. ¿Cuál es el contenido de cada objetos? el contenido es un uint32_t, un const uint32_t, un const uint8_t y uint8_t a los cuales les hemos dado nombres: previousMillis, interval, ledPin y ledState respectivamente.
- Las variables led definidas en task1 y task2 NO SON OBJETOS, son variables de tipo LED que permiten acceder al contenido de cada objeto.
- led es una variable propia de cada tarea.
- Note que las variables definidas en LED son privadas (private). Esto quiere decir que no vamos a acceder a ellas directamente. Ya veremos más abajo cómo modificar sus valores.
Nuestro nuevo tipo LED tiene un problema y es que no permite definir para cada LED creado el intervalo y el puerto donde se conectará.Para ello, se introduce el concepto de constructor de la clase. El constructor, permite definir los valores iniciales de cada objeto.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class LED{
private:
uint32_t previousMillis;
const uint32_t interval;
const uint8_t ledPin;
uint8_t ledState = LOW;
public:
LED(uint8_t _ledpin, uint32_t _interval): ledPin(_ledpin), interval(_interval) {
pinMode(_ledpin, OUTPUT);
previousMillis = 0;
}
};
|
El constructor de la clase es un método que recibe los valores iniciales del objeto y no devuelve nada.
Ahora si podemos definir cada objeto:
1 2 3 | void task1(){
static LED led(3,725);
}
|
1 2 | void task2(){
static LED led(5, 1360);
|
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 | class LED{
private:
uint32_t previousMillis;
const uint32_t interval;
const uint8_t ledPin;
uint8_t ledState = LOW;
public:
LED(uint8_t _ledpin, uint32_t _interval): ledPin(_ledpin), interval(_interval) {
pinMode(_ledpin, OUTPUT);
previousMillis = 0;
}
void toggleLED(){
uint32_t currentMillis = millis();
if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
};
|
Finalmente, al llamar toggleLED debemos indicar sobre qué objeto deberá actuar:
1 2 3 4 5 | void task1(){
static LED led(3,725);
led.toggleLED();
}
|
1 2 3 4 | void task2(){
static LED led(5, 1360);
led.toggleLED();
}
|
La versión final del código será:
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 | class LED{
private:
uint32_t previousMillis;
const uint32_t interval;
bool taskInit = false;
const uint8_t ledPin;
uint8_t ledState = LOW;
public:
LED(uint8_t _ledpin, uint32_t _interval): ledPin(_ledpin), interval(_interval) {
pinMode(_ledpin, OUTPUT);
previousMillis = 0;
}
void toggleLED(){
uint32_t currentMillis = millis();
if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
};
void setup() {
task1();
task2();
}
void task1(){
static LED led(3,1250);
led.toggleLED();
}
void task2(){
static LED led(5,375);
led.toggleLED();
}
void loop() {
task1();
task2();
}
|
Ejercicio 10¶
Podemos llevar un paso más allá el ejercicio anterior si añadimos el concepto de arreglo. ¿Para qué? Observa que el código de task1 y task2 es muy similar. Tal vez podamos resolver el problema usando únicamente una tarea:
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 | class LED{
private:
uint32_t previousMillis;
const uint32_t interval;
const uint8_t ledPin;
uint8_t ledState = LOW;
public:
LED(uint8_t _ledpin, uint32_t _interval): ledPin(_ledpin), interval(_interval) {
pinMode(_ledpin, OUTPUT);
previousMillis = 0;
}
void toggleLED(){
uint32_t currentMillis = millis();
if ( (currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
};
void setup() {
}
void task(){
static LED leds[2] = {{3,725},{5,1300}};
for(int i= 0; i < 2; i++){
leds[i].toggleLED();
}
}
void loop() {
task();
}
|
De nuevo, analiza el código anterior. Experimenta. ¿Está todo claro?
Ejercicio 11: miniRETO¶
¿Qué son los punteros? para entenderlos te propongo un mini RETO. Analiza en detalle el siguiente código
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 | void setup(){
Serial.begin(115200);
}
void processData(uint8_t *pData, uint8_t size, uint8_t *res){
uint8_t sum = 0;
for(int i= 0; i< size; i++){
sum = sum + *(pData+i) - 0x30;
}
*res = sum;
}
void loop(void){
static uint8_t rxData[10];
static uint8_t dataCounter = 0;
if(Serial.available() > 0){
rxData[dataCounter] = Serial.read();
dataCounter++;
if(dataCounter == 5){
uint8_t result = 0;
processData(rxData, dataCounter, &result);
dataCounter = 0;
Serial.println(result);
}
}
}
|
En la función loop se define un arreglo de enteros de 8 bits sin signo (uint8_t). A la función processData le estamos pasando la dirección del primer elemento del arreglo, la cantidad de datos almacenados en el arreglo y la dirección de la variable result, definida también en loop, donde se almacenará el resultado de processData. Nota que processData no retorna un valor y sin embargo, produce un resultado que puede guardarse en la variable result.
Las variables pData y res son punteros. Nota que al llamar processData estamos almacenando en esas variables la dirección del primer elemento del arreglo y la dirección de la variable result.
- ¿Qué crees entonces que son los punteros?
- ¿Para qué sirven los punteros?