Barcode reader with a TCD1304AP TOSHIBA CCD linear image sensor and NUCLEO-F103RB board.

Dependencies:   mbed zbar

Barcode Reader

Barcodes represent data by varying the widths of spaces and bars. These barcodes, now commonly referred to as linear or one-dimensional (1D), can be scanned by barcode readers. In this project a TCD1304AP TOSHIBA CCD linear image sensor is used to scan barcodes. The obtained light intensity stream is passed to the ZBar library streamlined for embedded use.

Flow charthttps://os.mbed.com/media/uploads/hudakz/barcodereader_diagram.png
TCD1304AP Driver

The TCD1304AP requires three clock signals (see below the Timing chart). It can operate with or without a built-in electronic shutter. In this project the electronic shutter is used to control the integration (exposure) time:

https://os.mbed.com/media/uploads/hudakz/barcodereader_timing.png




I used STM32CubeIDE to build the driver for the TCD1304AP sensor and then merged it with the Mbed OS 2 project.

STM32F103RB clock configuration
https://os.mbed.com/media/uploads/hudakz/barcodereader_clockconf.png


STM32F103RB pinout
https://os.mbed.com/media/uploads/hudakz/barcodereader_pinout.png


  • The signal for TCD1304AP's master clock (fiM) is generated by STM32F103's TIM1 timer and it is output at pin PA_8.
  • The Shift Gate clock signal (SH) is produced by timer TIM2 and it's available at pin PA_15.
  • The Integration Clear Gate pulses (ICG) are generated by timer TIM3 at pin PA_6.

The TCD1304AP's master clock runs at 1.714MHz. Pixel data is available at its output (OS) after each four pulses. To keep up with such speed DMA (Direct Memmory Access) is used to move the voltage data produced by the ADC (Analog to Digital Convertor) into the STM32F103's SRAM. The ADC is clocked with a 12MHz signal and runs in continuous mode.

https://os.mbed.com/media/uploads/hudakz/barcodereader_clocking.png

main.cpp

Committer:
hudakz
Date:
2020-01-10
Revision:
3:f2e67488f5ab
Parent:
2:415e979c52cf

File content as of revision 3:f2e67488f5ab:

/*
 * Barcode reader
 *
 * Target:  NUCLEO-F103RB
 * Sensor:  TCD1304AP(DG) - TOSHIBA CCD linear image sensor
 *
 * Author:  Zoltan Hudak
 * Date:    2020-01
 *
 * Depends on the ZBar library <http://zbar.sourceforge.net>
 *
 */
#include "main.h"
#include "mbed.h"
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <zbar.h>

using namespace zbar;

//#define TEST_DECODER  // Uncomment to carry out a decoder test
#define DATA_LEN        3694
#define MARGIN_LEFT     32
#define MARGIN_RIGHT    14
#define FWD             1
#define REV             0

class Handler : public Decoder::Handler
{
public:
    Handler()           { }
    virtual ~Handler()  { }
    virtual void decode_callback(Decoder& decoder)
    {
        if (decoder.get_type() <= ZBAR_PARTIAL) {
            return;
        }

        printf("%s%s:%s\n", decoder.get_symbol_name(), decoder.get_addon_name(), decoder.get_data_chars());
    }
};

ADC_HandleTypeDef   hadc1;
DMA_HandleTypeDef   hdma_adc1;
TIM_HandleTypeDef   htim1;
TIM_HandleTypeDef   htim2;
TIM_HandleTypeDef   htim3;
DigitalOut          led(LED1);
DigitalIn           btn(USER_BUTTON);
uint16_t            adcData[DATA_LEN];
volatile bool       adcDmaStarted = false;
volatile bool       adcDataAvailable = false;
volatile bool       ledIsOn = false;    // Use this rather than the 'led' variable to make ISR as fast as possible
Decoder*            decoder;
Handler*            handler;
Scanner*            scanner;

static void         MX_GPIO_Init();
static void         MX_ADC1_Init();
static void         MX_TIM2_Init();
static void         MX_DMA_Init();
static void         MX_TIM3_Init();
static void         MX_TIM1_Init();

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef* htim)
{
    if (htim->Instance == TIM3) {
        if (ledIsOn && !adcDmaStarted) {
            HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcData, DATA_LEN);
            adcDmaStarted = true;
        }
    }
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    adcDataAvailable = true;
}

#ifdef TEST_DECODER

/**
 * @brief
 * @note
 * @param
 * @retval
 */
static void test_decoder()
{
    printf("Decoding space/bar widths ...\n");

/*$off*/
    // Space/bar widths of the string 'WIKIPEDIA' encoded in Code-39 barcode
    unsigned    space_bar_widths[] =
    {
        9, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
        1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 2, 2,
        1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1,
        1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1,
        2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 9
    };
/*$on*/
    size_t  size = sizeof(space_bar_widths) / sizeof(*space_bar_widths);

    decoder->new_scan();

    for (size_t i = 0; i < size; i++) {
        decoder->decode_width(space_bar_widths[i]);
    }
}
#endif

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int main()
{
    printf("Starting ...\r\n");

    MX_GPIO_Init();
    MX_DMA_Init();
    MX_TIM1_Init();
    MX_TIM2_Init();
    MX_TIM3_Init();
    MX_ADC1_Init();
    HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);
    HAL_TIM_OC_Start(&htim2, TIM_CHANNEL_1);
    HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);
    decoder = new Decoder;
    handler = new Handler;
    scanner = new Scanner(decoder);
    decoder->set_handler(*handler);
    decoder->set_config(ZBAR_NONE, ZBAR_CFG_MIN_LEN, 0);

    while (1) {
        if ((btn == 0) && !ledIsOn) {
            led = 1;
            ledIsOn = true;
        }

        if (adcDataAvailable) {
            adcDataAvailable = false;
            led = 0;

            printf("---------------------------\n");

#ifdef TEST_DECODER
            test_decoder();
#else
            scanner->flush();
            scanner->flush();
            scanner->new_scan();

            uint16_t  black = 0;
            int       i;

            for (i = MARGIN_LEFT; i < DATA_LEN - MARGIN_RIGHT; i++) {
                if (adcData[i] > black)
                    black = adcData[i];
            }

            for (i = MARGIN_LEFT; i < DATA_LEN - MARGIN_RIGHT; i++) {
                scanner->scan_y(black - adcData[i]);
            }
#endif
            printf("Scan done.\r\n");
            wait_ms(300);   // Debauncing the button
            ledIsOn = false;
            adcDmaStarted = false;
        }
    }
}

/**
  * @brief ADC1 Initialization Function
  * @note
  * @param
  * @retval
  */
static void MX_ADC1_Init()
{
    ADC_ChannelConfTypeDef  sConfig = { 0 };
    /** Common config
      */
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK) {
        Error_Handler();
    }

    /** Configure Regular Channel
      */
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
        Error_Handler();
    }

    while (HAL_ADCEx_Calibration_Start(&hadc1) != HAL_OK);
}

/**
  * @brief TIM1 Initialization Function
  * @note
  * @param
  * @retval
  */
static void MX_TIM1_Init()
{
    TIM_ClockConfigTypeDef          sClockSourceConfig = { 0 };
    TIM_MasterConfigTypeDef         sMasterConfig = { 0 };
    TIM_OC_InitTypeDef              sConfigOC = { 0 };
    TIM_BreakDeadTimeConfigTypeDef  sBreakDeadTimeConfig = { 0 };

    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 21 - 1;
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim1) != HAL_OK) {
        Error_Handler();
    }

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) {
        Error_Handler();
    }

    if (HAL_TIM_OC_Init(&htim1) != HAL_OK) {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) {
        Error_Handler();
    }

    sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
    sConfigOC.Pulse = 0;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    if (HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
        Error_Handler();
    }

    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 0;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) {
        Error_Handler();
    }

    HAL_TIM_MspPostInit(&htim1);
}

/**
  * @brief TIM2 Initialization Function
  * @note
  * @param
  * @retval
  */
static void MX_TIM2_Init()
{
    TIM_SlaveConfigTypeDef  sSlaveConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };
    TIM_OC_InitTypeDef      sConfigOC = { 0 };

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 60 - 1;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
        Error_Handler();
    }

    if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) {
        Error_Handler();
    }

    sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
    sSlaveConfig.InputTrigger = TIM_TS_ITR0;
    if (HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig) != HAL_OK) {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {
        Error_Handler();
    }

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 30;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
        Error_Handler();
    }

    __HAL_TIM_DISABLE_OCxPRELOAD(&htim2, TIM_CHANNEL_1);

    HAL_TIM_MspPostInit(&htim2);
}

/**
  * @brief TIM3 Initialization Function
  * @note
  * @param
  * @retval
  */
static void MX_TIM3_Init()
{
    TIM_SlaveConfigTypeDef  sSlaveConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };
    TIM_OC_InitTypeDef      sConfigOC = { 0 };
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 0;
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 20 * 100 - 1;
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim3) != HAL_OK) {
        Error_Handler();
    }

    if (HAL_TIM_PWM_Init(&htim3) != HAL_OK) {
        Error_Handler();
    }

    sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
    sSlaveConfig.InputTrigger = TIM_TS_ITR1;
    if (HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig) != HAL_OK) {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) {
        Error_Handler();
    }

    sConfigOC.OCMode = TIM_OCMODE_PWM2;
    sConfigOC.Pulse = 1;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
        Error_Handler();
    }

    __HAL_TIM_DISABLE_OCxPRELOAD(&htim3, TIM_CHANNEL_1);

    HAL_TIM_MspPostInit(&htim3);
}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init()
{
    /* DMA controller clock enable */
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA1_Channel1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

/**
  * @brief GPIO Initialization Function
  * @note
  * @param
  * @retval
  */
static void MX_GPIO_Init()
{
    GPIO_InitTypeDef    GPIO_InitStruct = { 0 };

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /*Configure GPIO pin : PA2 */
    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /*Configure GPIO pin : PA3 */
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void HAL_TIMEx_CommutCallback(TIM_HandleTypeDef* htim)
{ }

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void Error_Handler()
{
    printf("Error handler called!\r\n");
}