Skip to content

output devices

Topic(s) of this week

  • output devices (videoreview)
    • Demonstrate workflows used in controlling an output device(s) with MCU board you have designed.

Hero shots

True spiral development:
sm

Assignments

Group assignment

  • Measure the power consumption of an output device.
  • Document your work on the group work page and reflect on your individual page what you learned.

Individual assignment

  • Add an output device to a microcontroller board you’ve designed and program it to do something.

What I think I already know

With my media engineering background I’m quite confident to say that I know enough about this subject to get through the week without brain meltdown again. I’m still exited as always. Because this is just fun. Getting things to move or light up is what makes real interaction possible. The magic of electronics is what it can DO. That implies output.

What I want to learn

I bought a round Oled display that I want to get going. I also took with me a E-Ink screen. Never done that before. Let’s see how the week will progress…

SSTM planning

Supply

Day Supply Tasks
Thursday 08:00-09:30 (train)
19:30-21:30 document and read on e-ink and round Oled displays
Friday 9:00-13:00 (fablab) try to get out of arduino
Saturday 11:00-13:00 try to get out of arduino
Sunday 12:00-13:00 document
Monday -
Tuesday 9:00-17:30 (fablab) round LCD display
Wednesday 9:00-11:00 (fablab) document
11:00-12:00 Local review
12:00-13:00 Regional review
14:00-17:00 Neil time

Tasks

Have you answered these questions?

  • Documented how you determined power consumption of an output device with your group.
  • Documented what you learned from interfacing output device(s) to microcontroller and controlling the device(s).
  • Linked to the board you made in a previous assignment or documented your design and fabrication process if you made a new board.
  • Explained the programming process/es you used.
  • Explained any problems you encountered and how you fixed them.
  • Included original source code and any new design files.
  • Included a ‘hero shot’ of your board.
  • Leave feedback in Nueval that this weekly is ready for evaluation.

WAAG session & Group assignment

At Waag we got a workshop on output devices from Henk and Michelle (and me :-)).
Lots about LEDs, displays, motors, solenoids, libraries etc.
For OLED displays, you should always check what driver is used. Usually it’s an SSD1306, which has a good library support. But sometimes you get different variations. Check before buying. The one in the fab inventory doesn’t state which driver it uses. It works with SSD1306 though…
On motors: Henk showed us the Modular things board. A PCB with 2 Toshiba TB67H451 H-bridges. It can drive a stepper motor directly from USB.
Another thing Henk showed us is Urumbu: Minimal Machine Building which was created during instructor bootcamp 2020. It’s a machine-building approach that significantly simplifies the hardware by moving all embedded configuration into real-time parallel programming. Here are the links: gitlab and paper.

Electrical energy (1)

Every output device requires energy to do something. To move, to turn, to light up, etc. In the International System of Units (SI), the unit of the energy is Joule (J). That could be any energy: Mechanical energy, chemical energy, nutritional energy, electrical energy. It’s all in joule.
In the electrical world, electrical energy is transported by means of charged particles: Electrons. The SI unit of this electric charge is Coulomb (C). Joule and Coulomb (Energy and electrical charge) are at the base of it all. But how do they relate to volts, amperes, watts and all that stuff?

Current

Well, at any given point in an electric circuit you can measure the amount of electric charge that passes that point. By definition, if 1 coulomb of charge passes a point in 1 second than we call this a current of 1 ampere (A): 1 A = 1 C / s.
As said above, Joule is the unit of energy. Meaning the capacity to do work. In mechanical systems, 1 joule is equal to the energy used to accelerate a body with a mass of 1 kg using 1 newton of force over a distance of 1 meter. Similarly, in electrical systems, 1 joule is by definition the amount of energy released in 1 second by a current of 1 ampere through a resistance of 1 ohm. Without resistance, current can flow freely. It wouldn’t cost any energy at all. The resistance slows the current down. Now it takes energy to push charge through it. You need joules to move electric charge.

Voltage

Moving electrical charge from one point to another can only happen if there is a force that provides the work needed to move that charge. This force is called voltage (V). An important thing to remember is that voltage is a difference between two points. A so called potential difference: a difference in potential between two points. Electrical current and can only happen if there is such a potential difference and there is a conductive path to travel along. No potential difference (no voltage) means no movement of charge (no current). No conductive path means no current too.
By definition the voltage between two points is 1 volt if 1 joule of energy is required to move 1 coulomb of electrical charge between these two points: 1 V = 1 J / 1 Q.

Power

When an output device consumes energy, joule, and it does so at a certain rate. This rate at which electrical energy is used over a period of time is called power (P). It’s a certain amount of joule per second (J / s).
We’ve seen above that voltage means joules per coulombs and current means coulombs per second. V = J / Q and I = Q / s. The product of these two is joules per second: V x I = (J / Q) x (Q / s) = J / s. So if power is in J / s and the product V x I is in J / s as well, that means they’re the same! P = V x I. We express power in Watt. 1 watt is defined as 1 joule per second.

Electrical energy (2)

So how much electrical energy will a device consume in total? How much joule does it need to do a given amount of work? It is the power of that device (amount of joule per second otherwise known as watt) multiplied by the time how long the device consumes that power. E = P x t. And that takes us back to where we started. With electric energy in joule.
Funnily enough, we usually don’t express electrical energy in joule. We express it in Wh: WattHour. Note there’s no slash in between. It’s not Watt per hour. It’s Watt times hour. E = P x t. So although you might think a Wh (or a kWh for that matter) has a factor time in it, it doesn’t! It’s just another way for us humans to not have to say joule. Because although we standardized an International System of Units, we don’t really have to use it in our day-to-day world, right?

Group assignment

Group assignment is to measure the power consumption of an output device.
Easiest way is to do this using a workbench power supply.
sm sm

We’re testing a little gearbox DC motor that Vera brought. When set to 5V it draws 43mA which means 0,22mW.

sm sm
sm
This is a USB measurement tool. We measured a SAMD11C14 with a LED that is slowly turning on and off using PWM.

Let’s now try a stepper motor. We have a drv8825 stepstick here:
sm

We’re going to connect this to a ESP32C3 Xiao that Vera made:
sm sm
polulu reference website for documentation

We use 17hs19-2004S1 stepper motor with the following pinout:
Black -> A1
Green -> A2
Red -> B1
Blue -> B2

On Veras board, we’re going to use D10 for STEP. D9 is for DIR.
Here’s how we connected things:
sm sm

Next is to program the ESP to do something. The code is in Veras repo, linked here..

This didn’t work initially. The thing was that the VRef of the driver board was set incorrectly. you can set it using the potentiometer on the driver board. According to this website it should be set at 0.5V so the current limit is 1A. If this thing is set incorrectly, weird stuff happens. It was set to 1.8V and the motor consumed >2A (went into the current limiter of the power supply). It even did that when the step input was pulled to ground! We measured the VRef with a multimeter between GND and the potentiometer (the area where you put the screwdriver in) and set it to 0.5V.
Power consumption of the stepper:
Voltage = 18V
Current = 160 mA
This results in a power consumption of 18 x 160m = 2.88W.

Now for microstepping. The DRV8825 has 3 pins labeled M0, M1 and M2 that can be used for microstepping. From the manual:
The resolution (step size) selector inputs (MODE0, MODE1, and MODE2) enable selection from the six step resolutions according to the table below. All three selector inputs have internal 100kΩ pull-down resistors, so leaving these three microstep selection pins disconnected results in full-step mode. For the microstep modes to function correctly, the current limit must be set low enough (see below) so that current limiting gets engaged. Otherwise, the intermediate current levels will not be correctly maintained, and the motor will skip microsteps.

This is how to set it:

MODE0 MODE1 MODE2 Microstep Resolution
Low Low Low Full step
High Low Low Half step
Low High Low 1/4 step
High High Low 1/8 step
Low Low High 1/16 step
High Low High 1/32 step
Low High High 1/32 step
High High High 1/32 step

So we’ve connected M0, M1 and M2 to VCC and told it to turn 90 degrees and back again:

Individual assignment

No more arduino

I wanted a way to get out of Arduino IDE. I’m hoping that VS code is an option. I know you can use VScode to program using the Arduino libraries using the vscode extension for Arduino. And that works pretty good! As you can see below I started using it and it’s much better in my opinion than Arduino IDE.
I also wondered if I could get out of the arduino framework. Because “professional embedded programmers” will not use Arduino framework right? But what do they use? Which framework is currently not-deprecated (meaning well supported), open source, “easy” on memory and fast because not bloated?
I can imagine this should be a framework created by the manufacturer of the microcontroller. In my case (and almost everybody else), this is Microchip.
BUT BEWARE: BIG BLACK RABBITHOLE AHEAD! I documented my work on VSCode with Embedded-ide extension here. TL;DR I could get it to work.
sm

Round LCD

I have a round LCD made by Waveshare that I want to try. If possible, I’d like to include this in my final project.
The LCD is a 240x240 pixel, 1.28 inch round LCD display module with 65K RGB color.

The manufacturers product site can be found here
And the manufacturers wiki site can be found here
sm sm
The LCD has an SPI interface, the onboard driver is a GC9A01. The datasheet can be found here.
It can operate on 3.3V or 5V.
The pinout when connecting to an Arduino Uno:

LCD pins UNO pins
VCC Power (3.3V / 5V input) 5V
GND Ground GND
DIN SPI data input –> MOSI D11
CLK SPI clock input –> SCK D13
CS Chip selection, low active D10
DC Data/Command selection (1=data,0=command) D7
RST Reset, low active D8
BL Backlight D9

Usually SPI has main-to-secondary communication (MOSI: Main Output Secondary Input) and Secondary-to-Main (MISO: Secondary Output Main Input). However as this display is an output device only, there’s no MISO line.

The CS pin is low active. Meaning the LCD will only react to communication if CS pin is low (connected to ground). In some datasheets, e.g. the one of my SAMD11C14, this is referred to as _SS: Secondary Select.

The RST pin is low active as well. Meaning that if it is pulled low, the display driver will reset. It should be high during normal operation.

DC pin selects if incoming signals should be treated as Command (DC=0) or Data (DC=1). So it’s setting the diplay in either Data or Command mode.

The BL pin controls the backlight of the LCD. It can be “just” high to enable it or use PWM to set the level.

This LCD supports 12/16/18-bit data bus MCU interface, namely the formats of RGB444, RGB565, RGB666, which are commonly used.
The way to address the individual pixels is to assume that is is a square screen with a circle inside. Only the content in this circle are used, other pixeldata is ignored.

On the arduino Uno it works nicely, I connected everything according to the waveshare wiki and loaded the 1LCD_1inch28 sketch:
sm

I connected it to my logic analyzer (Zeroplus Logic Cube, which uses LAP-C software) just to take a peek at the SPI bus. Setting it up was easy, did it according to this youtube video:

sm sm

Round LCD on SAMD11C14

Now let’s connect it to my SAMD11C14. Memory limit will still be an issue I think. Usually graphic stuff is large. And the SAMD11C14 isn’t.
I’m going to use the PCB that I created in weekly 06_embedded-programming. When making this PCB, I actually didn’t think about SPI. I’ve only added I2C to pinouts. However, you should be able to use those pins for SPI as well.

LCD pins SAMD11C14 pins
VCC Power (3.3V / 5V input) 3.3V
GND Ground GND
DIN SPI data input –> MOSI PA14 -> SERCOM 0 PAD 0
CLK SPI clock input –> SCK PA15 -> SERCOM 0 PAD 1
CS Chip selection, low active GND
DC Data/Command selection (1=data,0=command) PA2
RST Reset, low active 3.3V
BL Backlight PA4

The pinout of my SAMD11C14 board (top to bottom with USB on the left):

connector
VCC Power (3.3V)
PA14 MOSI
PA15 SCK
PA02 AIN2
PA04 AIN0
GND GND

To be able to use the SPI bus in arduino using ArduinoCore-fab-sam you’ll have to enable SPI:
sm
Note that depending on the choice here, that arduinocore will select certain pins that it’s going to use. This pinout is mentioned in the variant.h file:

/*
 * SPI Interfaces
 */
#if defined(ONE_SPI)
#define SPI_INTERFACES_COUNT 1
#else
#define SPI_INTERFACES_COUNT 0
#endif

#if defined(ONE_WIRE) && defined (ONE_SPI)
  #if defined PIN_MAP_STANDARD
    #define PIN_SPI_MISO         (30u)
    #define PIN_SPI_MOSI         (8u)
    #define PIN_SPI_SCK          (9u)
    #define PIN_SPI_SS           (31u)

  #elif defined PIN_MAP_COMPACT
    #define PIN_SPI_MISO         (10u)
    #define PIN_SPI_MOSI         (3u)
    #define PIN_SPI_SCK          (4u)
    #define PIN_SPI_SS           (11u)
  #endif

  #define PERIPH_SPI           sercom1
  #define PAD_SPI_TX           SPI_PAD_2_SCK_3
  #define PAD_SPI_RX           SERCOM_RX_PAD_0

// ONE_UART and ONE_SPI
#else
  #if defined PIN_MAP_STANDARD
    #define PIN_SPI_MISO         (14u)
    #define PIN_SPI_MOSI         (4u)
    #define PIN_SPI_SCK          (5u)
    #define PIN_SPI_SS           (15u)
  #elif defined PIN_MAP_COMPACT
    #define PIN_SPI_MISO         (5u)
    #define PIN_SPI_MOSI         (1u)
    #define PIN_SPI_SCK          (2u)
    #define PIN_SPI_SS           (5u)
  #endif

  #define PERIPH_SPI           sercom0
  #define PAD_SPI_TX           SPI_PAD_2_SCK_3
  #define PAD_SPI_RX           SERCOM_RX_PAD_0
#endif

static const uint8_t SS   = PIN_SPI_SS ;    // The SERCOM SS PAD is available on this pin but HW SS isn't used. Set here only for reference.
static const uint8_t MOSI = PIN_SPI_MOSI ;
static const uint8_t MISO = PIN_SPI_MISO ;
static const uint8_t SCK  = PIN_SPI_SCK ;

To save memory, I do not want UART. So I select NO_UART_ONE_WIRE_ONE_SPI. That means according to variant.h it’s going to want to use:

    #define PIN_SPI_MISO         (30u) = PA30 -> SERCOM 1 PAD 0
    #define PIN_SPI_MOSI         (8u) = PA8 -> SERCOM 1 PAD 2
    #define PIN_SPI_SCK          (9u) = PA9 -> SERCOM 1 PAD 3
    #define PIN_SPI_SS           (31u) = PA31 -> SERCOM 1 PAD 1

If you check the readme.mk in the GenericATsamD11C14A folder you’ll find the actual pinout:
sm
As mentioned in the table above, I don’t have these pins available. I have the SWD connected to PA30 and PA31. And I have a switch and a LED connected to PA8 and PA9.

I don’t even have enough pins to drive all LCD pins. But I’m not going to use CS and RST (as I only have 1 LCD so don’t care about Chip select and don’t want to reset it in this test setup), so tying them to GND and 3.3V.

I found this tutorial on dronebotworkshop.com where it is mentioned to use the TFT-eSPI library. Unfortunately the memory of my SAMD11C14 isn’t big enough to use this:
sm

c:/users/lku04/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: C:\Users\lku04\AppData\Local\Temp\arduino\sketches\CD78A09CECB8D8B153423A2A34B54B64/sketch_mar25a.ino.elf section `.text' will not fit in region `FLASH'
c:/users/lku04/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: region `FLASH' overflowed by 17088 bytes
collect2.exe: error: ld returned 1 exit status

exit status 1

Compilation error: exit status 1

So I’ll have to try to adapt the Arduino sketch that Waveshare made and that is working on my Arduino Uno.
Again that doesn’t work out:
sm
There’s stuff about initializer-string for array of chars is too long [-fpermissive] and error: 'dtostrf' was not declared in this scope. All having to do with the fonts. I guess some functionality isn’t ported to SAMD arduinocore?
Anyway, who needs fonts. Lets strip it out.
It’s still not happy.
sm
region FLASH overflowed by 7924 bytes. Yup. Memory limits. But who needs grapics. Let’s strip it out as well.
I also had to work a bit on the LCD_Driver file provided by Waveshare, as I don’t want to use reset or chip select. So stripped that out as well.
And in DEV_Config there’s still a serial.begin. But I don’t have UART, so that has to go.
And so forth. And so on.
Lot’s of time later I still can’t get it to work properly. And that obviously is because in variant.h the SPI pins are defined that I will not use. So changing those as well. Things become messy now. I don’t like it.
Well, what about this. I can fit it in the memory:
sm
And it compiles. But yeah. No. No display. I think the reason is that it is still outputting SPI on wrong pins. So let’s dive into that.

SPI on custom pins

Note:
To be able to get some better workflow I decided to move to VSCode with Arduino extension. This way I can right-click and “go to definition”, even further into the libraries. Including core libraries which is not possible in Arduino IDE.

There are 3 ways of changing pins in Arduino framework:

  1. Create your custom variant.h. See this post how to do this.
  2. Use softSPI instead of hardware SPI. See this post on seeedstudio
  3. don’t use any of that and be more hardcore :-)

I don’t know why, but I decided to do the 3nd one…

Fortunately I found this great article on stowlake.com that explains in detail how this works and provides a sample script. Just to make sure that this information doesn’t get lost, I decided to copy it in my repo.

I used the example provided by stowlake and adapted it to my needs. See here:

#include <SERCOM.h>
#include <wiring_private.h>

#define MY_SERCOM sercom0

static const uint8_t MY_MISO = 30;  // sercom1 PAD[0]
static const uint8_t MY_MOSI = 14; // sercom0 PAD[0]
static const uint8_t MY_SCK = 15; // sercom0 PAD[1]
static const uint8_t MY_SS = 9;

static const EPioType MY_MOSI_PIOTYPE = PIO_SERCOM;
static const EPioType MY_MISO_PIOTYPE = PIO_SERCOM;
static const EPioType MY_SCK_PIOTYPE = PIO_SERCOM;

static const SercomSpiTXPad MY_TXPAD = SPI_PAD_0_SCK_1; // PAD[0] is output is MOSI
static const SercomRXPad MY_RXPAD = SERCOM_RX_PAD_0;    // PAD[0] is input is MISO


void setup()
{
    pinPeripheral( MY_MISO, MY_MISO_PIOTYPE );
    pinPeripheral( MY_MOSI, MY_MOSI_PIOTYPE );
    pinPeripheral( MY_SCK, MY_SCK_PIOTYPE );
    pinMode( MY_SS, OUTPUT);
    digitalWrite( MY_SS, HIGH ); // HIGH means not selected

}
void loop()
{
    // Start up SPI
    MY_SERCOM.resetSPI();
    MY_SERCOM.initSPI(MY_TXPAD, MY_RXPAD, SPI_CHAR_SIZE_8_BITS, MSB_FIRST);
    MY_SERCOM.initSPIClock(SERCOM_SPI_MODE_3, 1000000);
    //  _description.sercom->initSPIClock(SERCOM_SPI_MODE_0, 4000000);
    MY_SERCOM.enableSPI();

    // Wake up secondary
    digitalWrite ( MY_SS, LOW );
    delay(10);
    // Exchange data
  const char *MASTER_MESSAGE = "|-- Hello world! --|";
  const int MASTER_MESSAGE_LEN = 20;

    for ( uint16_t count = 0; count < MASTER_MESSAGE_LEN; count++ )
    {
        MY_SERCOM.transferDataSPI(MASTER_MESSAGE[count]);
        delay(1);
    }

    // Turn off SPI
    MY_SERCOM.resetSPI();

    // Tell secondary to rest
    digitalWrite(MY_SS, HIGH);

  delay(1000);
}

Next, I hooked up a logic analyzer to my SAMD11C14 board and checked if I could get an Hello World!:
sm sm

yeey, it works!
So with SPI working on the correct pins, let’s adapt the LCD driver files and see if I can get the LCD to work.

Drawing images on Round LCD with SAMD11C14

After quite some work and debugging, I managed to rewrite the Waveshare example into a library that works! The library can be found in my repo (LCD_SPI_driver.h) The code in my ino file:

#include "LCD_SPI_driver.h"
#include "GUI_Paint.h"

void setup()
{
  digitalWrite ( MY_SS, LOW );
  Config_Init();
  LCD_Init();
  LCD_SetBacklight(1000);
  Paint_NewImage(LCD_WIDTH, LCD_HEIGHT, 0, BLACK); // init the image cache memory
}

void loop()
{
    // LED on (wake up secondary indicator)
    digitalWrite ( MY_SS, HIGH );
    delay(50);
    // Draw on the LCD
    Paint_Clear(BLACK);
    Paint_DrawCircle(120,120, 120, BLUE ,DOT_PIXEL_2X2,DRAW_FILL_EMPTY);
    Paint_DrawLine  (120, 0, 120, 12,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID);
    Paint_DrawLine  (120, 228, 120, 240,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID);
    Paint_DrawLine  (0, 120, 12, 120,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID);
    Paint_DrawLine  (228, 120, 240, 120,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID);
    Paint_DrawLine  (120, 120, 70, 70,YELLOW ,DOT_PIXEL_3X3,LINE_STYLE_SOLID);
    Paint_DrawLine  (120, 120, 176, 64,BLUE ,DOT_PIXEL_3X3,LINE_STYLE_SOLID);
    Paint_DrawLine  (120, 120, 120, 210,RED ,DOT_PIXEL_2X2,LINE_STYLE_SOLID); 

    // Tell secondary to rest
    // LED off (sleep secondary indicator)
    digitalWrite(MY_SS, LOW);
  delay(1000);  
}

sm

I tried adding back in the image, but that unfortunately doesn’t fit the SAMD11 memory.
region FLASH overflowed by 5644 bytes. This is much less overflow than with using the Waveshare driver. That overflowed by 7924 bytes. An 2280 byte improvement. I think this is because I don’t use Arduino’s spi.h.

Spiral development

Time for some true spiral development. Let’s see if I can draw spirals.
I asked Microsoft Copilot:
create the code to draw an archimedes spiral on a 240x240 display using arduino. Single pixels can be drawn using Paint_SetPixel() function, which requires an X and an Y coordinate.

The result was:

#define PI 3.14159265358979323846

void drawArchimedesSpiral() {
  int centerX = 120; // Center of the display
  int centerY = 120;
  float a = 0; // Spiral tightness factor
  float b = 2; // Distance between successive turnings

  for (float theta = 0; theta <= 10 * PI; theta += 0.01) {
    int r = a + b * theta; // Archimedes spiral equation r = a + b*theta
    int x = r * cos(theta) + centerX;
    int y = r * sin(theta) + centerY;

    // Ensure the pixel is within the display boundaries
    if (x >= 0 && x < 240 && y >= 0 && y < 240) {
      Paint_SetPixel(x, y);
    }
  }
}

Which looks nice, but it relies on floats (and sin() and cos() functions rely on that as well), so it doesn’t fit the SAMD11 memory.
So I asked:
with this arduino C code as base, please change it so cos() and sin() are using lookup tables. the goal is to prevent the usage of floats because of memory limitations.

The result:

#define TABLE_SIZE 360 // Define the size of the lookup table

// Pre-computed lookup tables for sin and cos
int sin_table[TABLE_SIZE];
int cos_table[TABLE_SIZE];

// Function to initialize the lookup tables
void init_trig_tables() {
  for (int i = 0; i < TABLE_SIZE; i++) {
    sin_table[i] = (int)(100 * sin(2 * M_PI * i / TABLE_SIZE)); // Multiply by 100 to keep some precision
    cos_table[i] = (int)(100 * cos(2 * M_PI * i / TABLE_SIZE));
  }
}

void drawArchimedesSpiral() {
  int centerX = 120; // Center of the display
  int centerY = 120;
  int a = 0; // Spiral tightness factor
  int b = 2; // Distance between successive turnings

  int theta = 0; // Angle

  for (int i = 0; i < 240 * 240; i++) {
    // Calculate the next point on the spiral
    int r = a + b * theta;
    int dx = centerX + r * cos_table[theta];
    int dy = centerY + r * sin_table[theta];

    // Draw a line from the current point to the next point
    Paint_SetPixel(dx, dy);

    // Update the angle
    theta = (theta + 1) % TABLE_SIZE; // Ensure theta stays within the bounds of the lookup table
  }
}

This again doesn’t fit the memory. region RAM overflowed by 756 bytes!

But the lookup table can be computed and added as static. I used this website to generate the table for me:

sm

This is the resulting code, which includes some color changing fun:

#include "LCD_SPI_driver.h"
#include "GUI_Paint.h"

// Pre-computed lookup tables for sin
const uint16_t sin_table[] = {
500, 509, 517, 526, 535, 544, 552, 561,
570, 578, 587, 595, 604, 612, 621, 629,
638, 646, 655, 663, 671, 679, 687, 695,
703, 711, 719, 727, 735, 742, 750, 758,
765, 772, 780, 787, 794, 801, 808, 815,
821, 828, 835, 841, 847, 854, 860, 866,
872, 877, 883, 889, 894, 899, 905, 910,
915, 919, 924, 929, 933, 937, 941, 946,
949, 953, 957, 960, 964, 967, 970, 973,
976, 978, 981, 983, 985, 987, 989, 991,
992, 994, 995, 996, 997, 998, 999, 999,
1000, 1000, 1000, 1000, 1000, 999, 999, 998,
997, 996, 995, 994, 992, 991, 989, 987,
985, 983, 981, 978, 976, 973, 970, 967,
964, 960, 957, 953, 949, 946, 941, 937,
933, 929, 924, 919, 915, 910, 905, 899,
894, 889, 883, 877, 872, 866, 860, 854,
847, 841, 835, 828, 821, 815, 808, 801,
794, 787, 780, 772, 765, 758, 750, 742,
735, 727, 719, 711, 703, 695, 687, 679,
671, 663, 655, 646, 638, 629, 621, 612,
604, 595, 587, 578, 570, 561, 552, 544,
535, 526, 517, 509, 500, 491, 483, 474,
465, 456, 448, 439, 430, 422, 413, 405,
396, 388, 379, 371, 362, 354, 345, 337,
329, 321, 313, 305, 297, 289, 281, 273,
265, 258, 250, 242, 235, 228, 220, 213,
206, 199, 192, 185, 179, 172, 165, 159,
153, 146, 140, 134, 128, 123, 117, 111,
106, 101, 95, 90, 85, 81, 76, 71,
67, 63, 59, 54, 51, 47, 43, 40,
36, 33, 30, 27, 24, 22, 19, 17,
15, 13, 11, 9, 8, 6, 5, 4,
3, 2, 1, 1, 0, 0, 0, 0,
0, 1, 1, 2, 3, 4, 5, 6,
8, 9, 11, 13, 15, 17, 19, 22,
24, 27, 30, 33, 36, 40, 43, 47,
51, 54, 59, 63, 67, 71, 76, 81,
85, 90, 95, 101, 106, 111, 117, 123,
128, 134, 140, 146, 153, 159, 165, 172,
179, 185, 192, 199, 206, 213, 220, 228,
235, 242, 250, 258, 265, 273, 281, 289,
297, 305, 313, 321, 329, 337, 345, 354,
362, 371, 379, 388, 396, 405, 413, 422,
430, 439, 448, 456, 465, 474, 483, 491};

const UWORD color_table[] {
  WHITE,
  BLACK, 
  BLUE,
  BRED,
  GRED,
  GBLUE,
  RED,
  MAGENTA,
  GREEN,
  CYAN,
  YELLOW,
  BROWN,
  BRRED, 
  GRAY,
  DARKBLUE,
  LIGHTBLUE,  
  GRAYBLUE, 
  LIGHTGREEN, 
  LGRAY,
  LGRAYBLUE,
  LBBLUE,
};

const int color_table_length = 20;

void setup()
{
    digitalWrite ( MY_SS, LOW );
  Config_Init();
  LCD_Init();
  LCD_SetBacklight(1000);
  Paint_NewImage(LCD_WIDTH, LCD_HEIGHT, 0, BLACK); // init the image cache memory
}

#define WIDTH 240
#define HEIGHT 240
#define CENTER_X WIDTH / 2
#define CENTER_Y HEIGHT / 2
#define SPIRAL_FACTOR (min(WIDTH, HEIGHT) / 2.0) / (360 * 5)  // Adjust this value to change the spacing between the spiral arms


void drawArchimedesSpiral(UWORD color, int angleOffset) { // this only appears in the 4th quadrant.
  for (int i = 0; i < 360 * 10; ++i) {  // Increase the multiplier to draw more turns of the spiral
    int r = SPIRAL_FACTOR * i;
    int x = CENTER_X + r * (sin_table[(i + 90 + angleOffset) % 360]-500) / 1000; // sin(x + 90) = cos(x)
    int y = CENTER_Y + r * (sin_table[(i + angleOffset) % 360]-500) / 1000;
    if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {  // Ensure the pixel is within the display
      Paint_SetPixel(x, y, color);
//      delay(1);
    }
  }
}

void loop()
{
  static int angleOffsetStepsize = 90;
    // LED on (wake up secondary indicator)
    digitalWrite ( MY_SS, HIGH );
    delay(10);
    // Draw on the LCD
  Paint_Clear(BLACK);
  for (int i = 0; i <= color_table_length; i++ ) {
    for (int angleOffset = 360; angleOffset > 0; angleOffset = angleOffset - angleOffsetStepsize) {
      drawArchimedesSpiral(color_table[i], angleOffset);
    }
  }

    // Tell secondary to rest
    // LED off (sleep secondary indicator)
    digitalWrite(MY_SS, LOW);
  delay(10);  
}

sm

repo link to weekly 09

What I learned this week

Successes

I managed to squeeze in the self-created driver for my round LCD display. It was great to see that working. I also found out more about the Arduino framework, specifically how to assign pins that aren’t assigned by default.
Eventually this is programmed in Arduino framework, but using VSCode. Which I start to like. It’s anyway much better than Arduino IDE.

Fails & Fixes

I guess the big thing this week was to try to get out of Arduino framework as well. I wanted to see which framework is currently not-deprecated (meaning well supported), open source, “easy” on memory and fast because not bloated. That didn’t turn out to be successful. Although I did manage to get quite far, I couldn’t get stuff working.