From the mysqli::prepare:
Note:
The markers are legal only in certain places in SQL statements. For
  example, they are allowed in the VALUES() list of an INSERT statement
  (to specify column values for a row), or in a comparison with a column
  in a WHERE clause to specify a comparison value.
However, they are not allowed for identifiers (such as table or column
  names), in the select list that names the columns to be returned by a
  SELECT statement, or to specify both operands of a binary operator
  such as the = equal sign...
Also, from the same source, referring to the sql statement:
You should not add a terminating semicolon or \g to the statement.
So, if you want to provide the wanted column name, you must do it using PHP variables. I wrote a solution, involving all the steps you should use when running db operations. I know, it's a lot but it's easy to follow. The extended and documented version is in the link I provided you in my comment, earlier.
Good luck.
<?php
/*
 * Enable internal report functions. This enables the exception handling, 
 * e.g. mysqli will not throw PHP warnings anymore, but mysqli exceptions 
 * (mysqli_sql_exception). They are catched in the try-catch block.
 * 
 * MYSQLI_REPORT_ERROR: Report errors from mysqli function calls.
 * MYSQLI_REPORT_STRICT: Throw a mysqli_sql_exception for errors instead of warnings.
 */
$mysqliDriver = new mysqli_driver();
$mysqliDriver->report_mode = (MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
    // Just test values.
    $taxon_subtyp = 'abc';
    $taxon_typ = 'def';
    $taxon_nam = '123xyz';
    /*
     * Build the sql statement using the printf() function.
     * Familiarize yourself with it (it takes 15 minutes),
     * because it is a very powerfull function, to use especially
     * in constructing complex sql statements.
     * 
     * In principle, each "%s" represents a placeholder for each 
     * variable in the variable list, that follows after the sql statement string.
     */
    $sql = sprintf('SELECT DISTINCT %s FROM refPlant WHERE %s = ?', $taxon_subtyp, $taxon_typ);
    // Open connection.
    $conn = openConnection();
    // Prepare and validate statement.
    $stmt = $conn->prepare($sql);
    if (!$stmt) {
        throw new Exception('Prepare error: ' . $conn->errno . ' - ' . $conn->error);
    }
    // Bind variables for the parameter markers (?) in the SQL statement.
    $bound = $stmt->bind_param('s', $taxon_nam);
    if (!$bound) {
        throw new Exception('Bind error: A variable could not be bound to the prepared statement');
    }
    // Execute the prepared SQL statement.
    $executed = $stmt->execute();
    if (!$executed) {
        throw new Exception('Execute error: The prepared statement could not be executed!');
    }
    // Get the result set from the prepared statement.
    $result = $stmt->get_result();
    if (!$result) {
        throw new Exception('Get result error: ' . $conn->errno . ' - ' . $conn->error);
    }
    // Get the number of rows in the result.
    $numberOfRows = $result->num_rows;
    // Fetch data and save it into an array.
    $fetchedData = array();
    if ($numberOfRows > 0) {
        // Use mysqli_result::fetch_all to fetch all rows at once.
        $fetchedData = $result->fetch_all(MYSQLI_ASSOC);
    }
    // Print results (in a cool formatted manner), just for testing.
    echo '<pre>' . print_r($fetchedData, TRUE) . '<pre>';
    /*
     * Free the memory associated with the result. You should 
     * always free your result when it is not needed anymore.
     */
    $result->close();
    /*
     * Close the prepared statement. It also deallocates the statement handle.
     * If the statement has pending or unread results, it cancels them 
     * so that the next query can be executed.
     */
    $stmtClosed = $stmt->close();
    if (!$stmtClosed) {
        throw new Exception('The prepared statement could not be closed!');
    }
    // Close db connection.
    $connClosed = $conn->close();
    if (!$connClosed) {
        throw new Exception('The db connection could not be closed!');
    }
} catch (mysqli_sql_exception $e) {
    echo 'Error: ' . $e->getCode() . ' - ' . $e->getMessage();
    exit();
} catch (Exception $e) {
    echo $e->getMessage();
    exit();
}
/*
 * Disable internal report functions.
 * 
 * MYSQLI_REPORT_OFF: Turns reporting off.
 */
$mysqliDriver->report_mode = MYSQLI_REPORT_OFF;
EDIT:
Since you posted your "white list", I thought you might want to see it in action in my code structure as well. Just for fun :-)
<?php
/*
 * Enable internal report functions. This enables the exception handling, 
 * e.g. mysqli will not throw PHP warnings anymore, but mysqli exceptions 
 * (mysqli_sql_exception). They are catched in the try-catch block.
 * 
 * MYSQLI_REPORT_ERROR: Report errors from mysqli function calls.
 * MYSQLI_REPORT_STRICT: Throw a mysqli_sql_exception for errors instead of warnings.
 * 
 * Put this somewhere, so that it fits in your global code structure.
 */
$mysqliDriver = new mysqli_driver();
$mysqliDriver->report_mode = (MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
function get_following_plant_group($taxon_typ, $taxon_nam) {
    $taxon_order = ['class', 'ord', 'family', 'genera'];
    if (in_array($taxon_typ, $taxon_order)) {
        $taxon_subtyp = $taxon_order[array_search($taxon_typ, $taxon_order) + 1];
        try {
            /*
             * Build the sql statement using the printf() function.
             * Familiarize yourself with it (it takes 15 minutes),
             * because it is a very powerfull function, to use especially
             * in constructing complex sql statements.
             * 
             * In principle, each "%s" represents a placeholder for each 
             * variable in the variable list, that follows after the sql statement string.
             */
            $sql = sprintf('SELECT DISTINCT %s FROM refPlant WHERE %s = ? ORDER BY ?', $taxon_subtyp, $taxon_typ);
            // Open connection.
            $conn = getBdd();
            $conn->set_charset('utf8');
            // Prepare and validate statement.
            $stmt = $conn->prepare($sql);
            if (!$stmt) {
                throw new Exception('Prepare error: ' . $conn->errno . ' - ' . $conn->error);
            }
            // Bind variables for the parameter markers (?) in the SQL statement.
            $bound = $stmt->bind_param('ss', $taxon_nam, $taxon_subtyp);
            if (!$bound) {
                throw new Exception('Bind error: A variable could not be bound to the prepared statement');
            }
            // Execute the prepared SQL statement.
            $executed = $stmt->execute();
            if (!$executed) {
                throw new Exception('Execute error: The prepared statement could not be executed!');
            }
            // Get the result set from the prepared statement.
            $result = $stmt->get_result();
            if (!$result) {
                throw new Exception('Get result error: ' . $conn->errno . ' - ' . $conn->error);
            }
            // Get the number of rows in the result.
            $numberOfRows = $result->num_rows;
            /*
             * Fetch data and save it into an array.
             * Use mysqli_result::fetch_assoc to fetch a row at a time.
             */
            $arr = [];
            if ($numberOfRows > 0) {
                while ($row = $result->fetch_assoc()) {
                    $arr[] = $row[$taxon_subtyp];
                }
            }
            // Print results (in a cool formatted manner), just for testing.
            // echo '<pre>' . print_r($arr, TRUE) . '<pre>';
            /*
             * Free the memory associated with the result. You should 
             * always free your result when it is not needed anymore.
             */
            $result->close();
            /*
             * Close the prepared statement. It also deallocates the statement handle.
             * If the statement has pending or unread results, it cancels them 
             * so that the next query can be executed.
             */
            $stmtClosed = $stmt->close();
            if (!$stmtClosed) {
                throw new Exception('The prepared statement could not be closed!');
            }
            // Close db connection.
            $connClosed = $conn->close();
            if (!$connClosed) {
                throw new Exception('The db connection could not be closed!');
            }
            $arr = [$taxon_subtyp, $arr];
            return(json_encode($arr));
        } catch (mysqli_sql_exception $e) {
            echo 'Error: ' . $e->getCode() . ' - ' . $e->getMessage();
            exit();
        } catch (Exception $e) {
            echo $e->getMessage();
            exit();
        }
    }
}
/*
 * Disable internal report functions.
 * 
 * MYSQLI_REPORT_OFF: Turns reporting off.
 * 
 * Put this somewhere, so that it fits in your global code structure.
 */
$mysqliDriver->report_mode = MYSQLI_REPORT_OFF;