Hi all,
We have a BMD Studio Micro camera, that we wanted to control for shading.
The otherwise fantastic SKAARHOJ CCU controllers were out of range given our budget.
Instead we decided to try our luck with the BMD Shield, combined with the nice little DIY kit from SKAARHOJ (FaderBoard & Joystick add-on for the Shield).
Initially, we tried going with the faderboard, which connects directly to the top of the shield.
The idea was to switch to different operating modes via the 3 buttons.
But - that doesn't work very well when you want to control different parameters with a fixed encoding fader/rotary knob, as we soon discovered!
Instead, I decided to try and use the Joystick, combined with a LCD for parameter readout.
To get the extra connections required for combining the BMD Shield, SKAARHOJ Joystick and LCD, we had to upgrade to a Arduino Mege (2560).
Thanks to a couple of posts on this forum, we managed to sort the wiring properly.
If you look at the schematic, you get a crude idea of the required wiring.
- CCU_Controller_Schema.jpg (283.74 KiB) Viewed 23315 times
Please note, that the BMD Shield is connected directly to the top of the Mega! So all wiring runs between Shield, JoystickBoard and LCD.
The LCD unit is just the model (LCM1602C) which was supplied in our Arduino Starterkit. It's a 16X2LCD.
- Mega_Shield_LCD.jpg (138.03 KiB) Viewed 23315 times
The code is based on the BMD library's example, "CustomJoystickControl".
As a cheap solution to a shading controller for the Micro, this setup performs great.
- DIY-project vs Ikegami CCP
- Ikegami_vs_DIY.jpg (45.45 KiB) Viewed 23315 times
- Code: Select all
#include <BMDSDIControl.h>
#include <LiquidCrystal.h>
#include <math.h>
#include <Wire.h>
/**
Blackmagic Design 3G-SDI Shield Sketch
Joystick control - JAHA 2017-05-05
This sketch controls a BMD cam via a BMD 3G-SDI shield,
which supports a SKAARHOJ joystick&buttons PCB and controls a SDI-connected BMD-camera.
The three buttons are mapped as follows:
- Button 1, which selects WB-mode
- Button 2, which selects and cycles through black/gamma/gain adj modes
- Button 3, which selects focus-mode on the camera
The joystick is mapped as follows:
- X axis adjusts the mode X parameter
- Y axis adjusts the mode Y parameter
- Pressing the Joystick resets all correction parameters.
*/
// Camera#
const int cameraNumber = 2;
// LCD pin mappings
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);
// Hardware pin mappings
const int joystickButtonPin = A0;
const int joystickXPin = A1;
const int joystickYPin = A2;
const int button1Pin = 5;
const int button2Pin = 6;
const int button3Pin = 7;
// Blackmagic Design SDI control shield globals
const int shieldAddress = 0x6E;
BMD_SDITallyControl_I2C sdiTallyControl(shieldAddress);
BMD_SDICameraControl_I2C sdiCameraControl(shieldAddress);
// Button debouncing globals
unsigned long lastStableButtonTime[32];
int rawButtonLevels[32];
int stableButtonLevels[32];
// Joystick sample rate limiter globals
unsigned long lastJoystickUpdateTime;
// Global joystick detection parameters
int currentJoystickX = 0;
int currentJoystickY = 0;
// Camera parameter adjustment-state, globals
// Manual WhiteBalance
float manualWbValueController = 6; //4000
const float manualWbValueSensHigh = 0.2; // joy max deflection adjust
const float manualWbValueSensNormal = 0.05; // joy normal deflection adjust
const float manualWbFloor = 0; // 2500
const float manualWbTop = 17; // 8000
const int16_t manualWbValues[18] = { 2500, 2800, 3000, 3200, 3400, 3600, 4000, 4500, 4800, 5000,5200, 5400, 5600, 6000, 6500, 7000, 7500, 8000 };
int16_t manualWbValueSendToDevice;
// Lift/Blacklevel
float liftValue = 0;
const float liftValueSensHigh = 0.005;
const float liftValueSensNormal = 0.001;
float liftValueArray[4] = { 0.0, 0.0, 0.0, 0.0 };
const float liftFloor = -2;
const float liftTop = 2;
// Gamma
float gammaValue = 0;
const float gammaValueSensHigh = 0.005;
const float gammaValueSensNormal = 0.001;
float gammaValueArray[4] = { 0.0, 0.0, 0.0, 0.0 };
const float gammaFloor = -4;
const float gammaTop = 4;
// Gain
float gainValue = 1;
const float gainValueSensHigh = 0.03;
const float gainValueSensNormal = 0.01;
float gainValueArray[4] = { 0.0, 0.0, 0.0, 0.0 };
const float gainFloor = 0;
const float gainTop = 16;
// MasterGain
float masterGainValueController = 1.0;
const float masterGainSensHigh = 0.1;
const float masterGainSensNormal = 0.05;
const int masterGainValues[5] = { 1, 2, 4, 8, 16 }; // gain x1, x2, x4, x8, x16
const int masterGainFloor = 0; //x1
const int masterGainTop = 4; //x16
int masterGainSendToDevice;
// Focus
float focusValue = 0.5;
const float focusValueSensHigh = 0.01;
const float focusValueSensNormal = 0.002;
const float focusFloor = 0.0;
const float focusTop = 1.0;
// ModeSelect - global parameter, determines operation-mode
// 1 - Whitebalance
// 2 - Lift (CC) & mastergain
// 3 - Gamma (CC) & mastergain
// 4 - Gain (CC) & mastergain
// 5 - Focus
int ModeSelect = 0; // current mode
int previousMode = 0; // previous mode
// ColorCorrection-mode
int currentCCMode = 1; // current ColorCorrectionMode: Lift, Gamma, Gain
bool ccMode; // flag true if CCmode is active
void setup() {
Serial.begin(9600);
Serial.println("startup");
lcd.begin(16, 2);
lcd.print("CCU controller");
// Configure digital inputs
pinMode(joystickButtonPin, INPUT_PULLUP);
pinMode(button1Pin, INPUT_PULLUP);
pinMode(button2Pin, INPUT_PULLUP);
pinMode(button3Pin, INPUT_PULLUP);
// Set up the BMD SDI control library
sdiTallyControl.begin();
sdiCameraControl.begin();
// The shield supports up to 400KHz, use faster
// I2C speed to reduce latency
Wire.setClock(400000);
// Enable both tally and control overrides
sdiTallyControl.setOverride(true);
sdiCameraControl.setOverride(true);
// reset camera to defaults
DoParameterUpdateResetAll();
// display welcome message
lcd.setCursor(0, 0);
lcd.print("CCU controller");
}
void loop() {
DetectOperationMode();
ReadJoystickInput();
DoParameterUpdate();
}
void DetectOperationMode() {
// Detect button1 - WhiteBalance
if (getButtonStableEdge(button1Pin) == true && getButtonStableLevel(button1Pin) == LOW) {
// When the button is low (pressed) the WB-mode is activated
ModeSelect = 1;
ccMode = false;
Serial.println("Mode1 detected"); //debug only
CheckDisplayClear();
}
// Detect button2, cycle through black/gamma/gain adjustment modes
if (getButtonStableEdge(button2Pin) == true && getButtonStableLevel(button2Pin) == LOW) {
// detect if we should step to next cc-mode
if (ccMode) {
currentCCMode = currentCCMode + 1;
if (currentCCMode > 3)
currentCCMode = 1;
}
ccMode = true;
if (currentCCMode == 1) {
ModeSelect = 2; // Lift
}
else if (currentCCMode == 2) {
ModeSelect = 3; // Gamma
}
else if (currentCCMode == 3) {
ModeSelect = 4; // Gain
}
Serial.println("Mode2, 3 or 4 detected"); // debug only
Serial.println(ModeSelect);
CheckDisplayClear();
}
// Detect button3, select focus mode
if (getButtonStableEdge(button3Pin) == true && getButtonStableLevel(button3Pin) == LOW) {
ModeSelect = 5; // Focus
ccMode = false;
Serial.println("Mode5 detected"); // debug only
CheckDisplayClear();
}
// If the reset button is pressed, clear all color correction values
if (getButtonStableEdge(joystickButtonPin) == true && getButtonStableLevel(joystickButtonPin) == LOW) {
DoParameterUpdateResetAll();
Serial.println("Reset detected");
}
}
void ReadJoystickInput() {
if (getJoystickUpdateReady() == true) {
switch (ModeSelect) {
case (0): // ignore
break;
case (1): // Whitebalance
Serial.println("ReadJoy > case 1");
// joystick Y = whitebalance K
currentJoystickY = getJoystickAxisPercent(joystickYPin);
if (currentJoystickY > 80) {
manualWbValueController = manualWbValueController + manualWbValueSensHigh; // using wb sensitivity-constant
}
else if (currentJoystickY > 0) {
manualWbValueController = manualWbValueController + manualWbValueSensNormal;
}
else if (currentJoystickY < -80) {
manualWbValueController = manualWbValueController - manualWbValueSensHigh;
}
else if (currentJoystickY < 0) {
manualWbValueController = manualWbValueController - manualWbValueSensNormal;
}
// Don't allow the values to exceed the minimum/maximum
manualWbValueController = constrain(manualWbValueController, manualWbFloor, manualWbTop);
break;
case (2): // CC Lift
Serial.println("ReadJoy > case 2");
// joystick Y = lift
currentJoystickY = getJoystickAxisPercent(joystickYPin);
if (currentJoystickY > 80) {
liftValue = liftValue + liftValueSensHigh; // using lift sensitivity-constant
}
else if (currentJoystickY > 0) {
liftValue = liftValue + liftValueSensNormal;
}
else if (currentJoystickY < -80) {
liftValue = liftValue - liftValueSensHigh;
}
else if (currentJoystickY < 0) {
liftValue = liftValue - liftValueSensNormal;
}
ReadJoystickInputMasterGain();
// Don't allow the values to exceed the minimum/maximum
liftValue = constrain(liftValue, liftFloor, liftTop);
break;
case (3): // CC Gamma
Serial.println("ReadJoy > case 3");
// joystick Y = gamma
currentJoystickY = getJoystickAxisPercent(joystickYPin);
if (currentJoystickY > 80) {
gammaValue = gammaValue + gammaValueSensHigh; // using gamma sensitivity-constant
}
else if (currentJoystickY > 0) {
gammaValue = gammaValue + gammaValueSensNormal;
}
else if (currentJoystickY < -80) {
gammaValue = gammaValue - gammaValueSensHigh;
}
else if (currentJoystickY < 0) {
gammaValue = gammaValue - gammaValueSensNormal;
}
ReadJoystickInputMasterGain();
// Don't allow the values to exceed the minimum/maximum
gammaValue = constrain(gammaValue, gammaFloor, gammaTop);
break;
case (4): // CC Gain
Serial.println("ReadJoy > case 4");
// joystick Y = gain
currentJoystickY = getJoystickAxisPercent(joystickYPin);
if (currentJoystickY > 80) {
gainValue = gainValue + gainValueSensHigh; // use gain sensitivity-constant
}
else if (currentJoystickY > 0) {
gainValue = gainValue + gainValueSensNormal;
}
else if (currentJoystickY < -80) {
gainValue = gainValue - gainValueSensHigh;
}
else if (currentJoystickY < 0) {
gainValue = gainValue - gainValueSensNormal;
}
ReadJoystickInputMasterGain();
// Don't allow the values to exceed the minimum/maximum
gainValue = constrain(gainValue, gainFloor, gainTop);
break;
case (5): // Focus
Serial.println("ReadJoy > case 5");
// joystick Y
currentJoystickY = getJoystickAxisPercent(joystickYPin);
if (currentJoystickY > 80) {
focusValue = focusValue + focusValueSensHigh; // use focus sensitivity-constant
}
else if (currentJoystickY > 0) {
focusValue = focusValue + focusValueSensNormal;
}
else if (currentJoystickY < -80) {
focusValue = focusValue - focusValueSensHigh;
}
else if (currentJoystickY < 0) {
focusValue = focusValue - focusValueSensNormal;
}
// Don't allow the values to exceed the minimum/maximum
focusValue = constrain(focusValue, focusFloor, focusTop);
break;
}
}
}
void ReadJoystickInputMasterGain() {
// joystick X = masterGain
currentJoystickX = getJoystickAxisPercent(joystickXPin);
if (currentJoystickX > 80) {
masterGainValueController = masterGainValueController + masterGainSensHigh;
}
else if (currentJoystickX > 0) {
masterGainValueController = masterGainValueController + masterGainSensNormal;
}
else if (currentJoystickX < -80) {
masterGainValueController = masterGainValueController - masterGainSensHigh;
}
else if (currentJoystickX < 0) {
masterGainValueController = masterGainValueController - masterGainSensNormal;
}
// Don't allow the values to exceed the minimum/maximum
masterGainValueController = constrain(masterGainValueController, masterGainFloor, masterGainTop);
}
void DoParameterUpdate() {
// Send new parameter adjustment to the camera
switch (ModeSelect) {
case (0): // ignore
break;
case (1): // Whitebalance
// joystick Y (manualWbValue)
DoParameterUpdateWhiteBalance();
break;
case (2): // CC Lift
// joystick Y = lift (liftValue)
// joystick X = masterGain
DoParameterUpdateLift();
DoParameterUpdateMasterGain();
break;
case (3): // CC Gamma
// joystick Y = gamma
// joystick X = masterGain
DoParameterUpdateGamma();
DoParameterUpdateMasterGain();
break;
case (4): // CC Gain
// joystick Y = gain
// joystick X = masterGain
DoParameterUpdateGain();
DoParameterUpdateMasterGain();
break;
case (5): // Focus
// joystick X
DoParameterUpdateFocus();
break;
}
}
void DoParameterUpdateWhiteBalance() {
Serial.println("ParameterUpdate > case 1");
manualWbValueSendToDevice = manualWbValues[round(manualWbValueController)];
sdiCameraControl.writeCommandInt16(
cameraNumber, // Destination: Camera number
1, // Category: Video
2, // Param: White balance
0, // Operation: Set Absolute,
manualWbValueSendToDevice // manualWbValue
);
UpdateDisplay(5, "WhiteBalance", manualWbValueSendToDevice);
}
void DoParameterUpdateLift() {
Serial.println("ParameterUpdate > Lift");
liftValueArray[0] = liftValue;
liftValueArray[1] = liftValue;
liftValueArray[2] = liftValue;
liftValueArray[3] = liftValue;
// set lift values
sdiCameraControl.writeCommandFixed16(
cameraNumber, // Destination: Camera number
8, // Category: Color Correction
0, // Param: Offset Adjust
0, // Operation: Set Absolute,
liftValueArray // lift values
);
UpdateDisplay(5, "Black", liftValue);
}
void DoParameterUpdateGamma() {
Serial.println("ParameterUpdate > Gamma");
gammaValueArray[0] = gammaValue;
gammaValueArray[1] = gammaValue;
gammaValueArray[2] = gammaValue;
gammaValueArray[3] = gammaValue;
// set gamma values
sdiCameraControl.writeCommandFixed16(
cameraNumber, // Destination: Camera number
8, // Category: Color Correction
1, // Param: Gamma adjust
0, // Operation: Set Absolute,
gammaValueArray // gamma value
);
UpdateDisplay(5, "Gamma", gammaValue);
}
void DoParameterUpdateGain() {
Serial.println("ParameterUpdate > Gain");
gainValueArray[0] = gainValue;
gainValueArray[1] = gainValue;
gainValueArray[2] = gainValue;
gainValueArray[3] = gainValue;
// set gain values
sdiCameraControl.writeCommandFixed16(
cameraNumber, // Destination: Camera number
8, // Category: Color Correction
2, // Param: Gain adjust
0, // Operation: Set Absolute,
gainValueArray // gain value
);
UpdateDisplay(5, "Gain", gainValue);
}
void DoParameterUpdateFocus() {
Serial.println("ParameterUpdate > focus");
// set focus values
sdiCameraControl.writeCommandFixed16(
cameraNumber, // Destination: Camera number
0, // Category: Lens
0, // Param: Focus
0, // Operation: Set Absolute,
focusValue // focus value
);
UpdateDisplay(1, "Focus", focusValue);
UpdateDisplay(3, "", focusValue);
}
void DoParameterUpdateResetAll() {
// Reset color correction
for (int i = 0; i < 10; i++) {
Serial.println("Reset camera parameters"); // looping to increase readability in serial-monitor
}
manualWbValueController = 6; //4000K
liftValue = 0.02;
gammaValue = 0.15;
gainValue = 1.4;
focusValue = 0.52;
masterGainValueController = 3;
DoParameterUpdateWhiteBalance();
DoParameterUpdateLift();
DoParameterUpdateGamma();
DoParameterUpdateGain();
DoParameterUpdateMasterGain();
DoParameterUpdateFocus();
lcd.clear();
/*
// Reset all color correction parameters in the camera
sdiCameraControl.writeCommandVoid(
cameraNumber, // Destination: Camera
8, // Category: Color Correction
7 // Param: Reset Defaults
);
// Request auto-iris adjustment in the camera
sdiCameraControl.writeCommandVoid(
cameraNumber, // Destination: Camera
0, // Category: Lens
5 // Param: Auto Iris
);
*/
}
void DoParameterUpdateMasterGain() {
masterGainSendToDevice = masterGainValues[round(masterGainValueController)];
sdiCameraControl.writeCommandInt8(
cameraNumber, // Destination: Camera number
1, // Category: Video
1, // Param: Sensor gain
0, // Operation: Set Absolute,
masterGainSendToDevice // masterGainValue
);
UpdateDisplay(6, "mGain", masterGainSendToDevice);
}
bool getJoystickUpdateReady() {
// Determines if we are ready for another joystick update (this is
// a rate limiter to ensure that the user can slowly adjust the
// parameters easily with the joystick)
unsigned long currentTime = millis();
if ((lastJoystickUpdateTime - currentTime) > 100) {
lastJoystickUpdateTime = currentTime;
return true;
}
return false;
}
int getJoystickAxisPercent(int analogPin) {
// Reads the joystick axis on the given analog pin as a [-100 - 100] scaled value
int rawAnalogValue = analogRead(analogPin);
int scaledAnalogValue = map(rawAnalogValue, 0, 1023, -100, 100);
// Consider values close to zero as zero, so that when the joystick is
// centered it reports zero even if it is slightly mis-aligned
if (abs(scaledAnalogValue) < 10) {
scaledAnalogValue = 0;
}
return scaledAnalogValue;
}
bool getButtonStableEdge(int digitalPin) {
// Detects debounced edges (i.e. presses and releases) of a button
bool previousLevel = stableButtonLevels[digitalPin];
bool newLevel = getButtonStableLevel(digitalPin);
return previousLevel != newLevel;
}
int getButtonStableLevel(int digitalPin) {
// Reads a digital pin and filters it, returning the stable button position
int pinLevel = digitalRead(digitalPin);
unsigned long currentTime = millis();
// If the button is rapidly changing (bouncing) during a press, keep
// resetting the last stable time count
if (pinLevel != rawButtonLevels[digitalPin]) {
lastStableButtonTime[digitalPin] = currentTime;
rawButtonLevels[digitalPin] = pinLevel;
}
// Once the button has been stable for
if ((currentTime - lastStableButtonTime[digitalPin]) > 20) {
stableButtonLevels[digitalPin] = pinLevel;
}
return stableButtonLevels[digitalPin];
}
void UpdateDisplay(int _position, char _text[], float _value) {
// split lcd-display into 6 areas:
// upper-left = 1
// upper-right = 2
// lower-left = 3
// lower-right = 4
// left upper+lower = 5
// right upper+lower = 6
if (_position <= 0 || _position > 6) {
Serial.print("lcd-position out of bounds");
return;
}
// /*
Serial.print(_text);
Serial.print(" ");
Serial.println(_value);
// */
switch (_position) {
case (1): // row = 0, column = 0, upper left
lcd.setCursor(0, 0);
if (_text == "") {
lcd.print(_value);
}
else {
lcd.print(_text);
}
break;
case (2): // row = 7, column = 0, upper right
lcd.setCursor(7, 0);
if (_text == "") {
lcd.print(_value);
}
else {
lcd.print(_text);
}
break;
case (3): // row = 0, column = 1, lower left
lcd.setCursor(0, 1);
if (_text == "") {
lcd.print(_value);
}
else {
lcd.print(_text);
}
break;
case (4): // row = 7, column = 1, lower right
lcd.setCursor(7, 1);
if (_text == "") {
lcd.print(_value);
}
else {
lcd.print(_text);
}
break;
case (5): // text + value, left part of lcd
lcd.setCursor(0, 0);
lcd.print(_text);
lcd.setCursor(0, 1);
lcd.print(_value);
break;
case (6): // text + value, right part of lcd
lcd.setCursor(7, 0);
lcd.print(_text);
lcd.setCursor(7, 1);
lcd.print(_value);
break;
}
}
void CheckDisplayClear() {
if (previousMode != ModeSelect) {
lcd.clear();
Serial.print("CLEAR CLEAR CLEAR CLEAR CLEAR CLEAR CLEAR CLEAR");
}
previousMode = ModeSelect;
}